-
Notifications
You must be signed in to change notification settings - Fork 2
/
build.js
350 lines (316 loc) · 9.29 KB
/
build.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
/**
* @version v1.3.1
* https://github.com/vue-mini/create-vue-mini
* 请谨慎修改此文件,改动过多可能会导致你后续升级困难。
*/
import path from 'node:path';
import process from 'node:process';
import fs from 'fs-extra';
import chokidar from 'chokidar';
import babel from '@babel/core';
import traverse from '@babel/traverse';
import t from '@babel/types';
import { minify } from 'terser';
import postcss from 'postcss';
import postcssrc from 'postcss-load-config';
import { rollup } from 'rollup';
import replace from '@rollup/plugin-replace';
import terser from '@rollup/plugin-terser';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import { green, bold } from 'kolorist';
import { getPackageInfo } from 'local-pkg';
import { createContext } from 'weapp-tailwindcss/core'
const { transformJs, transformWxml, transformWxss } = createContext({
rem2rpx: true
})
let topLevelJobs = [];
let bundleJobs = [];
const startTime = Date.now();
const NODE_ENV = process.env.NODE_ENV || 'production';
const __PROD__ = NODE_ENV === 'production';
const terserOptions = {
ecma: 2016,
toplevel: true,
safari10: true,
format: { comments: false },
};
let independentPackages = [];
async function findIndependentPackages() {
const { subpackages } = await fs.readJson(
path.resolve('src', 'app.json'),
'utf8',
);
if (subpackages) {
independentPackages = subpackages
.filter(({ independent }) => independent)
.map(({ root }) => root);
}
}
const builtLibraries = [];
const bundledModules = new Map();
async function bundleModule(module, pkg) {
const bundled = bundledModules.get(pkg);
if (
bundled?.has(module) ||
builtLibraries.some((library) => module.startsWith(library))
) {
return false;
}
if (bundled) {
bundled.add(module);
} else {
bundledModules.set(pkg, new Set([module]));
}
const {
packageJson: { peerDependencies },
} = await getPackageInfo(module);
const bundle = await rollup({
input: module,
external: peerDependencies ? Object.keys(peerDependencies) : undefined,
plugins: [
commonjs(),
replace({
preventAssignment: true,
values: {
'process.env.NODE_ENV': JSON.stringify(NODE_ENV),
},
}),
resolve(),
__PROD__ && terser(terserOptions),
].filter(Boolean),
});
await bundle.write({
exports: 'named',
file: `${pkg.replace('src', 'dist')}/miniprogram_npm/${module}/index.js`,
format: 'cjs',
});
return true;
}
function traverseAST(ast, pkg, babelOnly = false) {
traverse.default(ast, {
CallExpression({ node }) {
if (
node.callee.name !== 'require' ||
!t.isStringLiteral(node.arguments[0]) ||
node.arguments[0].value.startsWith('.') ||
(babelOnly && !node.arguments[0].value.startsWith('@babel/runtime'))
) {
return;
}
const module = node.arguments[0].value;
let promise = bundleModule(module, pkg);
if (babelOnly) {
promise = promise.then((valid) => {
if (!valid) return;
return Promise.all(
independentPackages.map((item) => {
const bundled = bundledModules.get(item);
if (bundled) {
bundled.add(module);
} else {
bundledModules.set(pkg, new Set([module]));
}
return fs.copy(
path.resolve('dist', 'miniprogram_npm', module),
path.resolve('dist', item, 'miniprogram_npm', module),
);
}),
);
});
}
bundleJobs?.push(promise);
},
});
}
async function buildComponentLibrary(name) {
const {
rootPath,
packageJson: { miniprogram },
} = await getPackageInfo(name);
let source = '';
if (miniprogram) {
source = path.join(rootPath, miniprogram);
} else {
try {
const dist = path.join(rootPath, 'miniprogram_dist');
const stats = await fs.stat(dist);
if (stats.isDirectory()) {
source = dist;
}
} catch {
// Empty
}
}
if (!source) return;
builtLibraries.push(name);
const destination = path.resolve('dist', 'miniprogram_npm', name);
await fs.copy(source, destination);
return new Promise((resolve) => {
const jobs = [];
const tnm = async (filePath) => {
const result = await babel.transformFileAsync(filePath, { ast: true });
traverseAST(result.ast, 'src', true);
const code = __PROD__
? (await minify(result.code, terserOptions)).code
: result.code;
await fs.writeFile(filePath, code);
};
const watcher = chokidar.watch([destination], {
ignored: (file, stats) => stats?.isFile() && !file.endsWith('.js'),
});
watcher.on('add', (filePath) => {
const promise = tnm(filePath);
jobs.push(promise);
});
watcher.on('ready', async () => {
const promise = watcher.close();
jobs.push(promise);
await Promise.all(jobs);
if (independentPackages.length > 0) {
await Promise.all(
independentPackages.map((item) =>
fs.copy(
destination,
path.resolve('dist', item, 'miniprogram_npm', name),
),
),
);
}
resolve();
});
});
}
async function scanDependencies() {
const { dependencies } = await fs.readJson('package.json', 'utf8');
for (const name of Object.keys(dependencies)) {
const promise = buildComponentLibrary(name);
topLevelJobs.push(promise);
}
}
async function processScript(filePath) {
let ast, code;
try {
const result = await babel.transformFileAsync(path.resolve(filePath), {
ast: true,
});
ast = result.ast;
code = result.code;
} catch (error) {
console.error(`Failed to compile ${filePath}`);
if (__PROD__) throw error;
console.error(error);
return;
}
const pkg = independentPackages.find((item) =>
filePath.startsWith(path.normalize(`src/${item}`)),
);
// The `src/` prefix is added to to distinguish `src` and `src/src`.
traverseAST(ast, pkg ? `src/${pkg}` : 'src');
const res = await transformJs(code)
code = res.code
if (__PROD__) {
code = (await minify(code, terserOptions)).code;
}
const destination = filePath.replace('src', 'dist').replace(/\.ts$/, '.js');
await fs.outputFile(destination, code);
}
async function processTemplate(filePath) {
const code = await fs.readFile(filePath, 'utf8')
const destination = filePath
.replace('src', 'dist')
.replace(/\.html$/, '.wxml');
const wxmlCode = await transformWxml(code)
await fs.outputFile(destination, wxmlCode, 'utf8');
}
async function processStyle(filePath) {
const source = await fs.readFile(filePath, 'utf8');
const { plugins, options } = await postcssrc({ from: filePath });
let css;
try {
const result = await postcss(plugins).process(source, options);
const { css: wxssCode } = await transformWxss(result.css);
css = wxssCode
} catch (error) {
console.error(`Failed to compile ${filePath}`);
if (__PROD__) throw error;
console.error(error);
return;
}
const destination = filePath
.replace('src', 'dist')
.replace(/\.css$/, '.wxss');
await fs.outputFile(destination, css);
}
const cb = async (filePath) => {
if (filePath.endsWith('.ts') || filePath.endsWith('.js')) {
await processScript(filePath);
// should process app.css for dev
await processStyle(path.resolve(import.meta.dirname, './src/app.css'));
return;
}
if (filePath.endsWith('.html')) {
await processTemplate(filePath);
// should process app.css for dev
await processStyle(path.resolve(import.meta.dirname, './src/app.css'));
return;
}
if (filePath.endsWith('.css')) {
await processStyle(filePath);
return;
}
await fs.copy(filePath, filePath.replace('src', 'dist'));
};
async function dev() {
await fs.remove('dist');
await findIndependentPackages();
await scanDependencies();
chokidar
.watch(['src'], {
ignored: (file, stats) =>
stats?.isFile() &&
(file.endsWith('.gitkeep') || file.endsWith('.DS_Store')),
})
.on('add', (filePath) => {
const promise = cb(filePath);
topLevelJobs?.push(promise);
})
.on('change', (filePath) => {
cb(filePath);
})
.on('ready', async () => {
await Promise.all(topLevelJobs);
await Promise.all(bundleJobs);
console.log(bold(green(`启动完成,耗时:${Date.now() - startTime}ms`)));
console.log(bold(green('监听文件变化中...')));
// Release memory.
topLevelJobs = null;
bundleJobs = null;
});
}
async function prod() {
await fs.remove('dist');
await findIndependentPackages();
await scanDependencies();
const watcher = chokidar.watch(['src'], {
ignored: (file, stats) =>
stats?.isFile() &&
(file.endsWith('.gitkeep') || file.endsWith('.DS_Store')),
});
watcher.on('add', (filePath) => {
const promise = cb(filePath);
topLevelJobs.push(promise);
});
watcher.on('ready', async () => {
const promise = watcher.close();
topLevelJobs.push(promise);
await Promise.all(topLevelJobs);
await Promise.all(bundleJobs);
console.log(bold(green(`构建完成,耗时:${Date.now() - startTime}ms`)));
});
}
if (__PROD__) {
await prod();
} else {
await dev();
}