1 | 'use strict'
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 | const {existsSync, readdirSync, statSync} = require('fs')
|
11 | const {join} = require('path')
|
12 | const rimraf = require('rimraf')
|
13 | const assert = require('assert')
|
14 | const {merge, get} = require('lodash')
|
15 | const signale = require('signale')
|
16 | const chalk = require('chalk')
|
17 | const inquirer = require('inquirer')
|
18 | const {fork} = require('child_process')
|
19 | const {babelBuild} = require('./babel')
|
20 | const rollup = require('./rollup')
|
21 | const {getExistFile, findPkgs, getConfig} = require('./utils')
|
22 | const getUserConfig = require('./get-user-config')
|
23 | const constants = require('./constants')
|
24 |
|
25 | function getBundleOpts(opts) {
|
26 | const {cwd, buildArgs = {}} = opts
|
27 | const entry = getExistFile({
|
28 | cwd,
|
29 | files: ['src/index.tsx', 'src/index.ts', 'src/index.jsx', 'src/index.js'],
|
30 | returnRelative: true,
|
31 | })
|
32 | const userConfig = getUserConfig({cwd})
|
33 | const userConfigs = Array.isArray(userConfig) ? userConfig : [userConfig]
|
34 |
|
35 | return userConfigs.map((config) => {
|
36 | const bundleOpts = merge(
|
37 | {
|
38 | entry,
|
39 | },
|
40 | buildArgs,
|
41 | config,
|
42 | )
|
43 |
|
44 | if (typeof bundleOpts.esm === 'string') {
|
45 | bundleOpts.esm = {type: bundleOpts.esm}
|
46 | }
|
47 | if (typeof bundleOpts.cjs === 'string') {
|
48 | bundleOpts.cjs = {type: bundleOpts.cjs}
|
49 | }
|
50 |
|
51 | return bundleOpts
|
52 | })
|
53 | }
|
54 |
|
55 | let workers = []
|
56 |
|
57 | function forkRollupBuild(option, pkg) {
|
58 | return new Promise((resolve, reject) => {
|
59 | try {
|
60 | const child = fork(join(__dirname, './fork-rollup.js'))
|
61 |
|
62 | child.on('exit', (code) => {
|
63 | if (code === 1) {
|
64 | reject(
|
65 | new Error(`[forkRollupBuild: ${pkg} type: ${option.type}] exit: ${code}`),
|
66 | )
|
67 | } else {
|
68 | signale.info(`[forkRollupBuild: ${pkg} type: ${option.type}] exit: ${code}`)
|
69 | resolve()
|
70 | }
|
71 | })
|
72 |
|
73 | child.on('error', (code) => {
|
74 | reject(new Error(`[forkRollupBuild: ${pkg} type: ${option.type}] error: ${code}`))
|
75 | })
|
76 |
|
77 | child.on('close', (code) => {
|
78 | if (code === 1) {
|
79 | reject(
|
80 | new Error(`[forkRollupBuild: ${pkg} type: ${option.type}] close: ${code}`),
|
81 | )
|
82 | } else {
|
83 | signale.info(`[forkRollupBuild: ${pkg} type: ${option.type}] close: ${code}`)
|
84 | resolve()
|
85 | }
|
86 | })
|
87 |
|
88 | signale.info(`[forkRollupBuild: ${pkg} type: ${option.type}] forked`)
|
89 |
|
90 | child.send(option)
|
91 |
|
92 | workers.push(child)
|
93 | } catch (e) {
|
94 | signale.error(e)
|
95 | process.exit()
|
96 | }
|
97 | })
|
98 | }
|
99 |
|
100 | process.on('SIGINT', () => {
|
101 | process.exit()
|
102 | })
|
103 |
|
104 | process.on('exit', () => {
|
105 | workers.forEach((worker) => {
|
106 | try {
|
107 | worker.kill()
|
108 | } catch (e) {
|
109 | signale.error('exit', e)
|
110 | }
|
111 | })
|
112 |
|
113 | workers = null
|
114 | })
|
115 |
|
116 | process.on('error', () => {
|
117 | workers.forEach((worker) => {
|
118 | try {
|
119 | worker.kill()
|
120 | } catch (e) {
|
121 | signale.error('error', e)
|
122 | }
|
123 | })
|
124 |
|
125 | workers = null
|
126 | })
|
127 |
|
128 | function validateBundleOpts(bundleOpts, {cwd, rootPath}) {
|
129 | if (bundleOpts.cjs && bundleOpts.cjs.lazy && bundleOpts.cjs.type === 'rollup') {
|
130 | throw new Error(
|
131 | `
|
132 | cjs.lazy don't support rollup.
|
133 | `.trim(),
|
134 | )
|
135 | }
|
136 | if (!bundleOpts.esm && !bundleOpts.cjs && !bundleOpts.umd) {
|
137 | throw new Error(
|
138 | `
|
139 | None format of ${chalk.cyan('cjs | esm | umd')} is configured
|
140 | `.trim(),
|
141 | )
|
142 | }
|
143 | if (bundleOpts.entry) {
|
144 | const tsConfigPath = join(cwd, 'tsconfig.json')
|
145 | const tsConfig =
|
146 | existsSync(tsConfigPath) || (rootPath && existsSync(join(rootPath, 'tsconfig.json')))
|
147 | if (
|
148 | !tsConfig &&
|
149 | ((Array.isArray(bundleOpts.entry) && bundleOpts.entry.some(isTypescriptFile)) ||
|
150 | (!Array.isArray(bundleOpts.entry) && isTypescriptFile(bundleOpts.entry)))
|
151 | ) {
|
152 | signale.info(
|
153 | `Project using ${chalk.cyan(
|
154 | 'typescript',
|
155 | )} but tsconfig.json not exists. Use default config.`,
|
156 | )
|
157 | }
|
158 | }
|
159 | }
|
160 |
|
161 | function isTypescriptFile(filePath) {
|
162 | return filePath.endsWith('.ts') || filePath.endsWith('.tsx')
|
163 | }
|
164 |
|
165 | async function build(opts, extraOpts = {}) {
|
166 | const {cwd, watch, rootPath, targetDir = 'lib/'} = opts
|
167 | const {pkg} = extraOpts
|
168 |
|
169 | function log(msg) {
|
170 | signale.info(`${pkg ? `[${pkg}] ` : ''}${msg}`)
|
171 | }
|
172 |
|
173 | const bundleOptsArray = getBundleOpts(opts)
|
174 | const buildArr = []
|
175 |
|
176 | for (const bundleOpts of bundleOptsArray) {
|
177 | validateBundleOpts(bundleOpts, {cwd, rootPath})
|
178 |
|
179 | log(`Clean ${targetDir} directory`)
|
180 | rimraf.sync(join(cwd, targetDir))
|
181 |
|
182 | if (bundleOpts.umd) {
|
183 | log('Build umd')
|
184 | buildArr.push(
|
185 | rollup({
|
186 | cwd,
|
187 | type: 'umd',
|
188 | targetDir,
|
189 | entry: bundleOpts.entry,
|
190 | watch,
|
191 | bundleOpts,
|
192 | }),
|
193 | )
|
194 | }
|
195 |
|
196 | if (bundleOpts.cjs) {
|
197 | const {cjs} = bundleOpts
|
198 | log(`Build cjs with ${cjs.type}`)
|
199 | if (cjs.type === 'babel') {
|
200 | buildArr.push(
|
201 | babelBuild({
|
202 | cwd,
|
203 | watch,
|
204 | rootPath,
|
205 | type: 'cjs',
|
206 | bundleOpts,
|
207 | libTargetDir: bundleOpts.libTargetDir,
|
208 | }),
|
209 | )
|
210 | } else {
|
211 | const option = {
|
212 | cwd,
|
213 | type: 'cjs',
|
214 | targetDir,
|
215 | entry: bundleOpts.entry,
|
216 | watch,
|
217 | bundleOpts,
|
218 | }
|
219 |
|
220 | if (get(bundleOpts, 'cjs.forkRollupBuild')) {
|
221 | buildArr.push(forkRollupBuild(option, pkg))
|
222 | } else {
|
223 | buildArr.push(rollup(option))
|
224 | }
|
225 | }
|
226 | }
|
227 |
|
228 | if (bundleOpts.esm) {
|
229 | const {esm} = bundleOpts
|
230 | log(`Build esm with ${esm.type}`)
|
231 | if (esm && esm.type === 'babel') {
|
232 | buildArr.push(
|
233 | babelBuild({
|
234 | cwd,
|
235 | watch,
|
236 | rootPath,
|
237 | type: 'esm',
|
238 | bundleOpts,
|
239 | libTargetDir: bundleOpts.libTargetDir,
|
240 | }),
|
241 | )
|
242 | } else {
|
243 | const option = {
|
244 | cwd,
|
245 | type: 'esm',
|
246 | targetDir,
|
247 | entry: bundleOpts.entry,
|
248 | watch,
|
249 | bundleOpts,
|
250 | }
|
251 |
|
252 | if (get(bundleOpts, 'esm.forkRollupBuild')) {
|
253 | buildArr.push(forkRollupBuild(option, pkg))
|
254 | } else {
|
255 | buildArr.push(rollup(option))
|
256 | }
|
257 | }
|
258 | }
|
259 | }
|
260 |
|
261 | await Promise.all(buildArr)
|
262 | }
|
263 |
|
264 | async function buildForLerna(opts) {
|
265 | const {cwd} = opts
|
266 |
|
267 | const userConfig = getUserConfig({cwd})
|
268 | const pkgs = readdirSync(join(opts.cwd, 'packages'))
|
269 | const buildArr = []
|
270 |
|
271 | for (const pkg of pkgs) {
|
272 | if (process.env.PACKAGE && pkg !== process.env.PACKAGE) continue
|
273 | const pkgPath = join(opts.cwd, 'packages', pkg)
|
274 | if (!statSync(pkgPath).isDirectory()) continue
|
275 | assert.ok(
|
276 | existsSync(join(pkgPath, 'package.json')),
|
277 | `package.json not found in packages/${pkg}`,
|
278 | )
|
279 | process.chdir(pkgPath)
|
280 | buildArr.push(
|
281 | build(
|
282 | {
|
283 | ...opts,
|
284 | buildArgs: merge(opts.buildArgs, userConfig),
|
285 | cwd: pkgPath,
|
286 | rootPath: cwd,
|
287 | },
|
288 | {
|
289 | pkg,
|
290 | },
|
291 | ),
|
292 | )
|
293 | }
|
294 |
|
295 | await Promise.all(buildArr)
|
296 | }
|
297 |
|
298 | async function runForInquirer(opts) {
|
299 | const {cwd, defaultCheck = true} = opts
|
300 |
|
301 | const userConfig = getUserConfig({cwd})
|
302 |
|
303 | const pkgs = findPkgs({
|
304 | cwd,
|
305 | packagesPatterns: userConfig.packagesPatterns,
|
306 | cb: (pkg) => {
|
307 | pkg.checked = defaultCheck
|
308 | pkg.name = pkg.pkgJson.name
|
309 | pkg.value = pkg.pkgJson.name
|
310 |
|
311 | return pkg
|
312 | },
|
313 | })
|
314 |
|
315 | if (!pkgs.length) {
|
316 | signale.info('没有要编译的包')
|
317 | process.exit(0)
|
318 | }
|
319 |
|
320 | const {checkPkgs = []} = await inquirer.prompt([
|
321 | {
|
322 | name: 'checkPkgs',
|
323 | message: '选择要运行的包',
|
324 | type: 'checkbox',
|
325 | choices: pkgs,
|
326 | },
|
327 | ])
|
328 |
|
329 | if (!checkPkgs.length) {
|
330 | signale.error('没有要运行的包')
|
331 | process.exit(1)
|
332 | }
|
333 |
|
334 | const buildArr = []
|
335 |
|
336 | for (const checkPkgName of checkPkgs) {
|
337 | const pkg = pkgs.find((item) => item.name === checkPkgName)
|
338 | if (!statSync(pkg.pkgPath).isDirectory()) continue
|
339 |
|
340 | process.chdir(pkg.pkgPath)
|
341 |
|
342 | buildArr.push(
|
343 | build(
|
344 | {
|
345 | ...opts,
|
346 | buildArgs: merge(opts.buildArgs, userConfig),
|
347 | cwd: pkg.pkgPath,
|
348 | rootPath: cwd,
|
349 | },
|
350 | {
|
351 | pkg: pkg.name,
|
352 | },
|
353 | ),
|
354 | )
|
355 | }
|
356 |
|
357 | await Promise.all(buildArr)
|
358 | }
|
359 |
|
360 | exports.runForInquirer = runForInquirer
|
361 |
|
362 | async function runForLerna(opts) {
|
363 | const useLerna = existsSync(join(opts.cwd, 'lerna.json'))
|
364 | if (useLerna && process.env.LERNA !== 'none') {
|
365 | await buildForLerna(opts)
|
366 | } else {
|
367 | await build(opts)
|
368 | }
|
369 | }
|
370 |
|
371 | exports.runForLerna = runForLerna
|
372 |
|
373 | async function runForPkgs(opts) {
|
374 | const {cwd, pkgs, rootUserConfig = getUserConfig({cwd})} = opts
|
375 |
|
376 | const runPkgs = []
|
377 |
|
378 | pkgs.forEach((pkg) => {
|
379 | if (get(pkg, 'pkgJson.hzLib.noBuild', false) !== true) {
|
380 | runPkgs.push(pkg)
|
381 | }
|
382 | })
|
383 |
|
384 | if (!runPkgs.length) {
|
385 | signale.info('没有要编译的包')
|
386 |
|
387 | return
|
388 | }
|
389 |
|
390 | const buildArr = []
|
391 |
|
392 | for (const pkg of runPkgs) {
|
393 | if (!statSync(pkg.pkgPath).isDirectory()) continue
|
394 | process.chdir(pkg.pkgPath)
|
395 | buildArr.push(
|
396 | build(
|
397 | {
|
398 | ...opts,
|
399 | buildArgs: merge(opts.buildArgs, rootUserConfig),
|
400 | cwd: pkg.pkgPath,
|
401 | rootPath: cwd,
|
402 | },
|
403 | {
|
404 | pkg: pkg.name,
|
405 | },
|
406 | ),
|
407 | )
|
408 | }
|
409 |
|
410 | await Promise.all(buildArr)
|
411 | }
|
412 |
|
413 | exports.runForPkgs = runForPkgs
|
414 |
|
415 | async function run(opts) {
|
416 | const {cwd} = opts
|
417 |
|
418 | const {runLibs = []} =
|
419 | getConfig({
|
420 | cwd,
|
421 | files: constants.localConfigFile,
|
422 | returnRelative: false,
|
423 | }) || {}
|
424 |
|
425 | if (runLibs.length) {
|
426 | const userConfig = getUserConfig({cwd})
|
427 | const pkgs = findPkgs({
|
428 | cwd,
|
429 | packagesPatterns: userConfig.packagesPatterns,
|
430 | cb: (pkg) => {
|
431 | pkg.name = pkg.pkgJson.name
|
432 |
|
433 | return pkg
|
434 | },
|
435 | })
|
436 |
|
437 | if (!pkgs.length) {
|
438 | signale.info('没有要编译的包')
|
439 | process.exit(0)
|
440 | }
|
441 |
|
442 | const buildArr = []
|
443 |
|
444 | for (const pkg of pkgs) {
|
445 | const index = runLibs.indexOf(pkg.name)
|
446 | if (index > -1 && statSync(pkg.pkgPath).isDirectory()) {
|
447 | process.chdir(pkg.pkgPath)
|
448 | buildArr.push(
|
449 | build(
|
450 | {
|
451 | ...opts,
|
452 | buildArgs: merge(opts.buildArgs, userConfig),
|
453 | cwd: pkg.pkgPath,
|
454 | rootPath: cwd,
|
455 | },
|
456 | {
|
457 | pkg: pkg.name,
|
458 | },
|
459 | ),
|
460 | )
|
461 | }
|
462 | }
|
463 |
|
464 | await Promise.all(buildArr)
|
465 | } else {
|
466 | runForInquirer(opts)
|
467 | }
|
468 | }
|
469 |
|
470 | exports.run = run
|