1 | "use strict";
|
2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
3 | return new (P || (P = Promise))(function (resolve, reject) {
|
4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
6 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
|
7 | step((generator = generator.apply(thisArg, _arguments || [])).next());
|
8 | });
|
9 | };
|
10 | Object.defineProperty(exports, "__esModule", { value: true });
|
11 | const fs = require("fs-extra");
|
12 | const path = require("path");
|
13 | const child_process_1 = require("child_process");
|
14 | const perf_hooks_1 = require("perf_hooks");
|
15 | const chokidar = require("chokidar");
|
16 | const chalk_1 = require("chalk");
|
17 | const _ = require("lodash");
|
18 | const klaw = require("klaw");
|
19 | const Util = require("./util");
|
20 | const config_1 = require("./config");
|
21 | const StyleProcess = require("./rn/styleProcess");
|
22 | const transformJS_1 = require("./rn/transformJS");
|
23 | const constants_1 = require("./util/constants");
|
24 | const convert_to_jdreact_1 = require("./jdreact/convert_to_jdreact");
|
25 |
|
26 | let isBuildingStyles = {};
|
27 | const styleDenpendencyTree = {};
|
28 | const depTree = {};
|
29 | const TEMP_DIR_NAME = 'rn_temp';
|
30 | const BUNDLE_DIR_NAME = 'bundle';
|
31 | class Compiler {
|
32 |
|
33 |
|
34 |
|
35 | constructor(appPath) {
|
36 | this.appPath = appPath;
|
37 | this.projectConfig = require(path.join(appPath, constants_1.PROJECT_CONFIG))(_.merge);
|
38 | const sourceDirName = this.projectConfig.sourceRoot || config_1.default.SOURCE_DIR;
|
39 | this.sourceDir = path.join(appPath, sourceDirName);
|
40 | this.entryFilePath = Util.resolveScriptPath(path.join(this.sourceDir, config_1.default.ENTRY));
|
41 | this.entryFileName = path.basename(this.entryFilePath);
|
42 | this.entryBaseName = path.basename(this.entryFilePath, path.extname(this.entryFileName));
|
43 | this.babel = this.projectConfig.babel;
|
44 | this.csso = this.projectConfig.csso;
|
45 | this.uglify = this.projectConfig.uglify;
|
46 | this.plugins = this.projectConfig.plugins;
|
47 | this.sass = this.projectConfig.sass;
|
48 | this.stylus = this.projectConfig.stylus;
|
49 | this.less = this.projectConfig.less;
|
50 | this.rnConfig = this.projectConfig.rn || {};
|
51 |
|
52 |
|
53 | if (this.rnConfig.outPath) {
|
54 | this.tempPath = path.resolve(this.appPath, this.rnConfig.outPath);
|
55 | if (!fs.existsSync(this.tempPath)) {
|
56 | throw new Error(`outPath ${this.tempPath} 不存在`);
|
57 | }
|
58 | this.hasJDReactOutput = true;
|
59 | }
|
60 | else {
|
61 | this.tempPath = path.join(appPath, TEMP_DIR_NAME);
|
62 | this.hasJDReactOutput = false;
|
63 | }
|
64 | }
|
65 | isEntryFile(filePath) {
|
66 | return path.basename(filePath) === this.entryFileName;
|
67 | }
|
68 | compileDepStyles(filePath, styleFiles) {
|
69 | if (isBuildingStyles[filePath] || styleFiles.length === 0) {
|
70 | return Promise.resolve({});
|
71 | }
|
72 | isBuildingStyles[filePath] = true;
|
73 | return Promise.all(styleFiles.map((p) => __awaiter(this, void 0, void 0, function* () {
|
74 | const filePath = path.join(p);
|
75 | const fileExt = path.extname(filePath);
|
76 | Util.printLog("compile" , _.camelCase(fileExt).toUpperCase(), filePath);
|
77 | return StyleProcess.loadStyle({
|
78 | filePath,
|
79 | pluginsConfig: {
|
80 | sass: this.sass,
|
81 | less: this.less,
|
82 | stylus: this.stylus
|
83 | }
|
84 | }, this.appPath);
|
85 | }))).then(resList => {
|
86 | return Promise.all(resList.map(item => {
|
87 | return StyleProcess.postCSS(Object.assign({}, item, { projectConfig: this.projectConfig }));
|
88 | }));
|
89 | }).then(resList => {
|
90 | const styleObjectEntire = {};
|
91 | resList.forEach(item => {
|
92 | const styleObject = StyleProcess.getStyleObject({ css: item.css, filePath: item.filePath });
|
93 |
|
94 | StyleProcess.validateStyle({ styleObject, filePath: item.filePath });
|
95 | Object.assign(styleObjectEntire, styleObject);
|
96 | if (filePath !== this.entryFilePath) {
|
97 | Object.assign(styleObjectEntire, _.get(styleDenpendencyTree, [this.entryFilePath, 'styleObjectEntire'], {}));
|
98 | }
|
99 | styleDenpendencyTree[filePath] = {
|
100 | styleFiles,
|
101 | styleObjectEntire
|
102 | };
|
103 | });
|
104 | return JSON.stringify(styleObjectEntire, null, 2);
|
105 | }).then(css => {
|
106 | let tempFilePath = filePath.replace(this.sourceDir, this.tempPath);
|
107 | const basename = path.basename(tempFilePath, path.extname(tempFilePath));
|
108 | tempFilePath = path.join(path.dirname(tempFilePath), `${basename}_styles.js`);
|
109 | StyleProcess.writeStyleFile({ css, tempFilePath });
|
110 | }).catch((e) => {
|
111 | throw new Error(e);
|
112 | });
|
113 | }
|
114 | initProjectFile() {
|
115 |
|
116 | const appJsonObject = Object.assign({
|
117 | name: _.camelCase(require(path.join(this.appPath, 'package.json')).name)
|
118 | }, this.rnConfig.appJson);
|
119 | const indexJsStr = `
|
120 | import {AppRegistry} from 'react-native';
|
121 | import App from './${this.entryBaseName}';
|
122 | import {name as appName} from './app.json';
|
123 |
|
124 | AppRegistry.registerComponent(appName, () => App);`;
|
125 | fs.writeFileSync(path.join(this.tempPath, 'index.js'), indexJsStr);
|
126 | Util.printLog("generate" , 'index.js', path.join(this.tempPath, 'index.js'));
|
127 | fs.writeFileSync(path.join(this.tempPath, 'app.json'), JSON.stringify(appJsonObject, null, 2));
|
128 | Util.printLog("generate" , 'app.json', path.join(this.tempPath, 'app.json'));
|
129 | return Promise.resolve();
|
130 | }
|
131 | processFile(filePath) {
|
132 | return __awaiter(this, void 0, void 0, function* () {
|
133 | if (!fs.existsSync(filePath)) {
|
134 | return;
|
135 | }
|
136 | const dirname = path.dirname(filePath);
|
137 | const distDirname = dirname.replace(this.sourceDir, this.tempPath);
|
138 | let distPath = path.format({ dir: distDirname, base: path.basename(filePath) });
|
139 | const code = fs.readFileSync(filePath, 'utf-8');
|
140 | if (constants_1.REG_STYLE.test(filePath)) {
|
141 |
|
142 | }
|
143 | else if (constants_1.REG_SCRIPTS.test(filePath)) {
|
144 | if (/\.jsx(\?.*)?$/.test(filePath)) {
|
145 | distPath = distPath.replace(/\.jsx(\?.*)?$/, '.js');
|
146 | }
|
147 | if (constants_1.REG_TYPESCRIPT.test(filePath)) {
|
148 | distPath = distPath.replace(/\.(tsx|ts)(\?.*)?$/, '.js');
|
149 | }
|
150 | Util.printLog("compile" , _.camelCase(path.extname(filePath)).toUpperCase(), filePath);
|
151 |
|
152 | const transformResult = transformJS_1.parseJSCode({
|
153 | code, filePath, isEntryFile: this.isEntryFile(filePath), projectConfig: this.projectConfig
|
154 | });
|
155 | const jsCode = transformResult.code;
|
156 | fs.ensureDirSync(distDirname);
|
157 | fs.writeFileSync(distPath, Buffer.from(jsCode));
|
158 | Util.printLog("generate" , _.camelCase(path.extname(filePath)).toUpperCase(), distPath);
|
159 |
|
160 | const styleFiles = transformResult.styleFiles;
|
161 | depTree[filePath] = styleFiles;
|
162 | yield this.compileDepStyles(filePath, styleFiles);
|
163 | }
|
164 | else {
|
165 | fs.ensureDirSync(distDirname);
|
166 | Util.printLog("copy" , _.camelCase(path.extname(filePath)).toUpperCase(), filePath);
|
167 | fs.copySync(filePath, distPath);
|
168 | Util.printLog("generate" , _.camelCase(path.extname(filePath)).toUpperCase(), distPath);
|
169 | }
|
170 | });
|
171 | }
|
172 | |
173 |
|
174 |
|
175 |
|
176 | buildTemp() {
|
177 | return new Promise((resolve, reject) => {
|
178 | const filePaths = [];
|
179 | this.processFile(this.entryFilePath).then(() => {
|
180 | klaw(this.sourceDir)
|
181 | .on('data', file => {
|
182 | if (!file.stats.isDirectory()) {
|
183 | filePaths.push(file.path);
|
184 | }
|
185 | })
|
186 | .on('error', (err, item) => {
|
187 | console.log(err.message);
|
188 | console.log(item.path);
|
189 | })
|
190 | .on('end', () => {
|
191 | Promise.all(filePaths.filter(f => f !== this.entryFilePath).map(filePath => this.processFile(filePath)))
|
192 | .then(() => {
|
193 | if (!this.hasJDReactOutput) {
|
194 | this.initProjectFile();
|
195 | resolve();
|
196 | }
|
197 | else {
|
198 | resolve();
|
199 | }
|
200 | });
|
201 | });
|
202 | });
|
203 | });
|
204 | }
|
205 | buildBundle() {
|
206 | fs.ensureDirSync(TEMP_DIR_NAME);
|
207 | process.chdir(TEMP_DIR_NAME);
|
208 |
|
209 | if (this.rnConfig.bundleType === 'jdreact') {
|
210 | console.log();
|
211 | console.log(chalk_1.default.green('生成JDReact 目录:'));
|
212 | console.log();
|
213 | convert_to_jdreact_1.convertToJDReact({
|
214 | tempPath: this.tempPath, entryBaseName: this.entryBaseName
|
215 | });
|
216 | return;
|
217 | }
|
218 |
|
219 | fs.ensureDirSync(BUNDLE_DIR_NAME);
|
220 | child_process_1.execSync(`node ../node_modules/react-native/local-cli/cli.js bundle --entry-file ./${TEMP_DIR_NAME}/index.js --bundle-output ./${BUNDLE_DIR_NAME}/index.bundle --assets-dest ./${BUNDLE_DIR_NAME} --dev false`, { stdio: 'inherit' });
|
221 | }
|
222 | perfWrap(callback, args) {
|
223 | return __awaiter(this, void 0, void 0, function* () {
|
224 | isBuildingStyles = {};
|
225 |
|
226 | const t0 = perf_hooks_1.performance.now();
|
227 | yield callback(args);
|
228 | const t1 = perf_hooks_1.performance.now();
|
229 | Util.printLog("compile" , `编译完成,花费${Math.round(t1 - t0)} ms`);
|
230 | console.log();
|
231 | });
|
232 | }
|
233 | watchFiles() {
|
234 | const watcher = chokidar.watch(path.join(this.sourceDir), {
|
235 | ignored: /(^|[/\\])\../,
|
236 | persistent: true,
|
237 | ignoreInitial: true
|
238 | });
|
239 | watcher
|
240 | .on('ready', () => {
|
241 | console.log();
|
242 | console.log(chalk_1.default.gray('初始化完毕,监听文件修改中...'));
|
243 | console.log();
|
244 | })
|
245 | .on('add', filePath => {
|
246 | const relativePath = path.relative(this.appPath, filePath);
|
247 | Util.printLog("create" , '添加文件', relativePath);
|
248 | this.perfWrap(this.buildTemp.bind(this));
|
249 | })
|
250 | .on('change', filePath => {
|
251 | const relativePath = path.relative(this.appPath, filePath);
|
252 | Util.printLog("modify" , '文件变动', relativePath);
|
253 | if (constants_1.REG_SCRIPTS.test(filePath)) {
|
254 | this.perfWrap(this.processFile.bind(this), filePath);
|
255 | }
|
256 | if (constants_1.REG_STYLE.test(filePath)) {
|
257 | _.forIn(depTree, (styleFiles, jsFilePath) => {
|
258 | if (styleFiles.indexOf(filePath) > -1) {
|
259 | this.perfWrap(this.processFile.bind(this), jsFilePath);
|
260 | }
|
261 | });
|
262 | }
|
263 | })
|
264 | .on('unlink', filePath => {
|
265 | const relativePath = path.relative(this.appPath, filePath);
|
266 | Util.printLog("unlink" , '删除文件', relativePath);
|
267 | this.perfWrap(this.buildTemp.bind(this));
|
268 | })
|
269 | .on('error', error => console.log(`Watcher error: ${error}`));
|
270 | }
|
271 | }
|
272 | exports.Compiler = Compiler;
|
273 | function hasRNDep(appPath) {
|
274 | const pkgJson = require(path.join(appPath, 'package.json'));
|
275 | return Boolean(pkgJson.dependencies['react-native']);
|
276 | }
|
277 | function updatePkgJson(appPath) {
|
278 | const version = Util.getPkgVersion();
|
279 | const RNDep = `{
|
280 | "@tarojs/components-rn": "^${version}",
|
281 | "@tarojs/taro-rn": "^${version}",
|
282 | "@tarojs/taro-router-rn": "^${version}",
|
283 | "@tarojs/taro-redux-rn": "^${version}",
|
284 | "react": "16.8.0",
|
285 | "react-native": "0.59.9",
|
286 | "redux": "^4.0.0",
|
287 | "tslib": "^1.8.0"
|
288 | }
|
289 | `;
|
290 | return new Promise((resolve, reject) => {
|
291 | const pkgJson = require(path.join(appPath, 'package.json'));
|
292 |
|
293 | if (!hasRNDep(appPath)) {
|
294 | pkgJson.dependencies = Object.assign({}, pkgJson.dependencies, JSON.parse(RNDep.replace(/(\r\n|\n|\r|\s+)/gm, '')));
|
295 | fs.writeFileSync(path.join(appPath, 'package.json'), JSON.stringify(pkgJson, null, 2));
|
296 | Util.printLog("generate" , 'package.json', path.join(appPath, 'package.json'));
|
297 | installDep(appPath).then(() => {
|
298 | resolve();
|
299 | });
|
300 | }
|
301 | else {
|
302 | resolve();
|
303 | }
|
304 | });
|
305 | }
|
306 | function installDep(path) {
|
307 | return new Promise((resolve, reject) => {
|
308 | console.log();
|
309 | console.log(chalk_1.default.yellow('开始安装依赖~'));
|
310 | process.chdir(path);
|
311 | let command;
|
312 | if (Util.shouldUseYarn()) {
|
313 | command = 'yarn';
|
314 | }
|
315 | else if (Util.shouldUseCnpm()) {
|
316 | command = 'cnpm install';
|
317 | }
|
318 | else {
|
319 | command = 'npm install';
|
320 | }
|
321 | child_process_1.exec(command, (err, stdout, stderr) => {
|
322 | if (err)
|
323 | reject();
|
324 | else {
|
325 | console.log(stdout);
|
326 | console.log(stderr);
|
327 | }
|
328 | resolve();
|
329 | });
|
330 | });
|
331 | }
|
332 | function build(appPath, buildConfig) {
|
333 | return __awaiter(this, void 0, void 0, function* () {
|
334 | const { watch, port } = buildConfig;
|
335 | process.env.TARO_ENV = "rn" ;
|
336 | yield Util.checkCliAndFrameworkVersion(appPath, "rn" );
|
337 | const compiler = new Compiler(appPath);
|
338 | fs.ensureDirSync(compiler.tempPath);
|
339 | const t0 = perf_hooks_1.performance.now();
|
340 | if (!hasRNDep(appPath)) {
|
341 | yield updatePkgJson(appPath);
|
342 | }
|
343 | try {
|
344 | yield compiler.buildTemp();
|
345 | }
|
346 | catch (e) {
|
347 | throw e;
|
348 | }
|
349 | const t1 = perf_hooks_1.performance.now();
|
350 | Util.printLog("compile" , `编译完成,花费${Math.round(t1 - t0)} ms`);
|
351 |
|
352 | if (compiler.rnConfig.onlyTaroToRn)
|
353 | return;
|
354 | if (watch) {
|
355 | compiler.watchFiles();
|
356 | if (!compiler.hasJDReactOutput) {
|
357 | startServerInNewWindow({ port, appPath });
|
358 | }
|
359 | }
|
360 | else {
|
361 | compiler.buildBundle();
|
362 | }
|
363 | });
|
364 | }
|
365 | exports.build = build;
|
366 |
|
367 |
|
368 |
|
369 |
|
370 | function startServerInNewWindow({ port = 8081, appPath }) {
|
371 |
|
372 | const isWindows = /^win/.test(process.platform);
|
373 | const scriptFile = isWindows
|
374 | ? 'launchPackager.bat'
|
375 | : 'launchPackager.command';
|
376 | const packagerEnvFilename = isWindows ? '.packager.bat' : '.packager.env';
|
377 | const portExportContent = isWindows
|
378 | ? `set RCT_METRO_PORT=${port}`
|
379 | : `export RCT_METRO_PORT=${port}`;
|
380 |
|
381 | const scriptsDir = path.resolve(appPath, './node_modules', 'react-native', 'scripts');
|
382 | const launchPackagerScript = path.resolve(scriptsDir, scriptFile);
|
383 | const procConfig = { cwd: scriptsDir };
|
384 | const terminal = process.env.REACT_TERMINAL;
|
385 |
|
386 | const packagerEnvFile = path.join(appPath, 'node_modules', 'react-native', 'scripts', packagerEnvFilename);
|
387 |
|
388 | fs.writeFileSync(packagerEnvFile, portExportContent, {
|
389 | encoding: 'utf8',
|
390 | flag: 'w'
|
391 | });
|
392 | if (process.platform === 'darwin') {
|
393 | if (terminal) {
|
394 | return child_process_1.spawnSync('open', ['-a', terminal, launchPackagerScript], procConfig);
|
395 | }
|
396 | return child_process_1.spawnSync('open', [launchPackagerScript], procConfig);
|
397 | }
|
398 | else if (process.platform === 'linux') {
|
399 | if (terminal) {
|
400 | return child_process_1.spawn(terminal, ['-e', 'sh ' + launchPackagerScript], procConfig);
|
401 | }
|
402 | return child_process_1.spawn('sh', [launchPackagerScript], procConfig);
|
403 | }
|
404 | else if (/^win/.test(process.platform)) {
|
405 | procConfig.stdio = 'ignore';
|
406 | return child_process_1.spawn('cmd.exe', ['/C', launchPackagerScript], procConfig);
|
407 | }
|
408 | else {
|
409 | console.log(chalk_1.default.red(`Cannot start the packager. Unknown platform ${process.platform}`));
|
410 | }
|
411 | }
|