반응형

사내 디자인 시스템을 만들고 있었는데

배포 및 개발 환경 관련해서 여러가지 피드백 및 요청이 왔습니다..!

 

1. 배포 환경을 갖추기 어려운데 자동화 스크립트가 있으면 좋을거 같다.

2. 초심자도 쉽게 배포하고 빌드하면 좋을 것 같다.

 

특히, 각 컴포넌트를 하나의 모노 레포지토리로 관리하고 있고 내부에서 
turbo repo, rollup, jest 등 많은 개발 라이브러리를 사용하고 있는데 시행착오를 많이 겪으시는거 같아서 

boilerplate 생성 스크립트를 하나 작성했고, 블로그에도 공유해 봅니다. 

 

생성 스크립트

 

더보기
const fs = require('fs')
const path = require('path')
const readline = require('readline')
const { exec } = require('child_process')

const validatePackageName = (name) => {
  const regex = /^@TDS\/[a-zA-Z0-9-_]+$/
  if (!regex.test(name)) {
    throw new Error(`Invalid package name format. It should be in the format @TDS/package's name`)
  }

  const hasUppercase = /[A-Z]/.test(name.split('/')[1])
  if (hasUppercase) {
    console.warn('Warning: Package name should not contain uppercase letters.')
  }
}

const generatePackageJson = (packageName, sourcePath) => {
  // 무엇인지 인지한 상태에서 바꿀것
  const [namespace, name] = packageName.split('/')
  
  const packageJson = {
    name: `${namespace}/${name.toLowerCase()}`,
    version: '0.0.0',
    sideEffects: ['./index.scss'],
    license: 'MIT',
    exports: {
      '.': {
        require: sourcePath,
        import: sourcePath,
      },
      './index.scss': './index.scss',
      './package.json': './package.json',
    },
    source: sourcePath,
    main: sourcePath,
    module: sourcePath,
    types: sourcePath,
    files: ['dist/**'],
    scripts: {
      test: 'jest --passWithNoTests',
      build: 'rollup -c && tsc --emitDeclarationOnly --declarationMap false --declaration --declarationDir dist/types',
      lint: `eslint "src/**/*.ts*"`,
      clean: `rm -rf .turbo && rm -rf node_modules && rm -rf dist`,
      'manual-release': 'pnpm run build && pnpm publish --no-git-checks',
    },
    devDependencies: {
      '@types/node': '^20.12.7',
      autoprefixer: '^10.4.19',
      'babel-jest': '29.5.0',
      eslint: '^8.57.0',
      'jest-config': 'workspace:*',
      'rollup-config': 'workspace:*',
      'eslint-config-acme': 'workspace:*',
      postcss: '^8.4.38',
      sass: '^1.75.0',
      'ts-jest': '29.0.5',
      tsconfig: 'workspace:*',
    },
    dependencies: {},
    peerDependencies: {
      '@types/react': '^18.2.37',
      '@types/react-dom': '^18.2.25',
      react: '^16.8 || ^17.0 || ^18.0',
    },
    peerDependenciesMeta: {
      '@types/react': {
        optional: true,
      },
      '@types/react-dom': {
        optional: true,
      },
    },

    publishConfig: {
      access: 'restricted',
      registry: 'http://##addressissecret##/api/v4/projects/311/packages/npm/',
      exports: {
        '.': {
          import: {
            types: `./dist/types/index.d.ts`,
            default: `./dist/es/client/${path.basename(sourcePath).replace('tsx', 'mjs')}`,
          },
          require: {
            types: `./dist/types/index.d.ts`,
            default: `./dist/cjs/client/${path.basename(sourcePath).replace('tsx', 'cjs')}`,
          },
        },
        './index.css': `./dist/es/client/index.css`,
      },
      source: `./src/index.ts`,
      main: `./dist/cjs/client/${path.basename(sourcePath).replace('tsx', 'cjs')}`,
      module: `./dist/es/client/${path.basename(sourcePath).replace('tsx', 'mjs')}`,
      types: `./dist/types/index.d.ts`,
    },
  }

  return JSON.stringify(packageJson, null, 2)
}
const createSrcIndexTsx = (srcDir) => {
  const srcIndexTsxContent = `export {};`
  fs.writeFileSync(path.join(srcDir, 'index.tsx'), srcIndexTsxContent)
}

const createRootIndexTsx = (projectDir) => {
  const rootIndexTsxContent = `import './index.scss';

// export * from './src';
`
  fs.writeFileSync(path.join(projectDir, 'index.tsx'), rootIndexTsxContent)
}

const createEslintConfig = (projectDir) => {
  const rootIndexTsxContent = `module.exports = {
    root: true,
    extends: ["acme"],
  };  
`
  fs.writeFileSync(path.join(projectDir, '.eslintrc.js'), rootIndexTsxContent)
}

const createIndexScss = (projectDir) => {
  const indexScssContent = ` 
`
  fs.writeFileSync(path.join(projectDir, 'index.scss'), indexScssContent)
}

const createJestConfig = (projectDir) => {
  const jestConfigContent = `const jestConfig = require('jest-config/jest.config.js')

const customJestConfig = {
  ...jestConfig,
  // 패키지별 설정을 여기에 추가
}

module.exports = customJestConfig
`
  fs.writeFileSync(path.join(projectDir, 'jest.config.js'), jestConfigContent)
}

const createPostcssConfig = (projectDir) => {
  const postcssConfigContent = `module.exports = {
  plugins: [require('autoprefixer')()],
}
`
  fs.writeFileSync(path.join(projectDir, 'postcss.config.js'), postcssConfigContent)
}

const createRollupConfig = (projectDir) => {
  const rollupConfigContent = `import { defaultConfig } from 'rollup-config/rollup.config.mjs'

const config = defaultConfig()
export default config
`
  fs.writeFileSync(path.join(projectDir, 'rollup.config.mjs'), rollupConfigContent)
}

const createSetupTests = (projectDir) => {
  const setupTestsContent = `import '@testing-library/jest-dom'
`
  fs.writeFileSync(path.join(projectDir, 'setupTests.ts'), setupTestsContent)
}

const createTsconfig = (projectDir) => {
  const tsconfigContent = `{
  "extends": "tsconfig/react-library.json",
  "compilerOptions": {
    "baseUrl": "./",

    "paths": {}
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
  "exclude": ["dist", "build", "node_modules", "**/*.test.*"]
}
`
  fs.writeFileSync(path.join(projectDir, 'tsconfig.json'), tsconfigContent)
}

const createProjectStructure = (packageName, projectDir, sourcePath) => {
  if (!fs.existsSync(projectDir)) {
    fs.mkdirSync(projectDir, { recursive: true })
  }

  const srcDir = path.join(projectDir, 'src')
  if (!fs.existsSync(srcDir)) {
    fs.mkdirSync(srcDir)
  }

  // Write package.json
  fs.writeFileSync(path.join(projectDir, 'package.json'), generatePackageJson(packageName, sourcePath))

  // Create other files
  createSrcIndexTsx(srcDir)
  createRootIndexTsx(projectDir)
  createIndexScss(projectDir)
  createEslintConfig(projectDir)
  createJestConfig(projectDir)
  createPostcssConfig(projectDir)
  createRollupConfig(projectDir)
  createSetupTests(projectDir)
  createTsconfig(projectDir)

  console.log(`Project structure for ${path.basename(projectDir)} created successfully.`)
}

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
})

const question = (text, defaultValue = '') => {
  return new Promise((resolve) => {
    rl.question(`${text}${defaultValue ? ` (${defaultValue})` : ''}: `, (answer) => resolve(answer || defaultValue))
  })
}

const runPnpmInstall = (projectDir) => {
  return new Promise((resolve, reject) => {
    exec('pnpm install', { cwd: './' }, (error, stdout, stderr) => {
      if (error) {
        console.error(`Error running pnpm install: ${error}`)
        reject(error)
        return
      }
      console.log(stdout)
      console.error(stderr)
      resolve()
    })
  })
}

;(async () => {
  const packageName = await question('Enter the package name (ex- @TDS/button)')
  validatePackageName(packageName)

  const folderPath = path.join('./apps/', packageName.replace(/\//g, '-'))

  console.log(`folders create in ${folderPath}`)
  const sourcePath = './index.tsx'
  const projectDir = path.join(folderPath)

  console.log(`entryPoint is ${sourcePath}`)

  createProjectStructure(packageName, projectDir, sourcePath)

  rl.close()
  console.log(`package.json generated and project folder created at ${projectDir}.`)

  console.log('start installing packages....')

  await runPnpmInstall(projectDir)

  console.log('done.')
})()

 

코드 전체는 아래에서 구경하실 수 있습니다. 


https://gist.github.com/lodado/4beb14f44d304fe65772809778e0fb35

 

generatePacakges.js

GitHub Gist: instantly share code, notes, and snippets.

gist.github.com

 

사용 예시 스크린샷)

 

 

기본 사용 방법은

라이브러리의 이름을 입력하면 지정된 위치에 폴더 +  라이브러리 명으로 하나의 레포지토리 및 설정 파일이 생깁니다.

혹시 쓰실 분은 config 관련 함수에 자기가 원하는 코드를 작성하는 식으로 커스텀하면 되실 것 같습니다. 

 

반응형

+ Recent posts