UNPKG

17.9 kBJavaScriptView Raw
1"use strict";
2var __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};
10Object.defineProperty(exports, "__esModule", { value: true });
11const fs = require("fs-extra");
12const path = require("path");
13const child_process_1 = require("child_process");
14const perf_hooks_1 = require("perf_hooks");
15const chokidar = require("chokidar");
16const chalk_1 = require("chalk");
17const _ = require("lodash");
18const klaw = require("klaw");
19const Util = require("./util");
20const config_1 = require("./config");
21const StyleProcess = require("./rn/styleProcess");
22const transformJS_1 = require("./rn/transformJS");
23const constants_1 = require("./util/constants");
24const convert_to_jdreact_1 = require("./jdreact/convert_to_jdreact");
25// import { Error } from 'tslint/lib/error'
26let isBuildingStyles = {};
27const styleDenpendencyTree = {};
28const depTree = {};
29const TEMP_DIR_NAME = 'rn_temp';
30const BUNDLE_DIR_NAME = 'bundle';
31class Compiler {
32 // babelConfig: any
33 // pxTransformConfig
34 // pathAlias
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 // this.babelConfig = this.projectConfig.plugins.babel // 用来配置 babel
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" /* 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 // validate styleObject
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 // generator app.json
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" /* 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" /* 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 // do something
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" /* COMPILE */, _.camelCase(path.extname(filePath)).toUpperCase(), filePath);
151 // transformJSCode
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" /* GENERATE */, _.camelCase(path.extname(filePath)).toUpperCase(), distPath);
159 // compileDepStyles
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" /* COPY */, _.camelCase(path.extname(filePath)).toUpperCase(), filePath);
167 fs.copySync(filePath, distPath);
168 Util.printLog("generate" /* GENERATE */, _.camelCase(path.extname(filePath)).toUpperCase(), distPath);
169 }
170 });
171 }
172 /**
173 * @description 编译文件,安装依赖
174 * @returns {Promise}
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 // 通过 jdreact 构建 bundle
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 // 默认打包到 bundle 文件夹
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" /* 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" /* 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" /* 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" /* UNLINK */, '删除文件', relativePath);
267 this.perfWrap(this.buildTemp.bind(this));
268 })
269 .on('error', error => console.log(`Watcher error: ${error}`));
270 }
271}
272exports.Compiler = Compiler;
273function hasRNDep(appPath) {
274 const pkgJson = require(path.join(appPath, 'package.json'));
275 return Boolean(pkgJson.dependencies['react-native']);
276}
277function 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 // 未安装 RN 依赖,则更新 pkgjson,并重新安装依赖
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" /* GENERATE */, 'package.json', path.join(appPath, 'package.json'));
297 installDep(appPath).then(() => {
298 resolve();
299 });
300 }
301 else {
302 resolve();
303 }
304 });
305}
306function 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}
332function build(appPath, buildConfig) {
333 return __awaiter(this, void 0, void 0, function* () {
334 const { watch, port } = buildConfig;
335 process.env.TARO_ENV = "rn" /* RN */;
336 yield Util.checkCliAndFrameworkVersion(appPath, "rn" /* 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" /* COMPILE */, `编译完成,花费${Math.round(t1 - t0)} ms`);
351 // rn 配置添加onlyTaroToRn字段,支持项目构建只编译不打包
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}
365exports.build = build;
366/**
367 * @description run packager server
368 * copy from react-native/local-cli/runAndroid/runAndroid.js
369 */
370function startServerInNewWindow({ port = 8081, appPath }) {
371 // set up OS-specific filenames and commands
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 // set up the launchpackager.(command|bat) file
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 // set up the .packager.(env|bat) file to ensure the packager starts on the right port
386 const packagerEnvFile = path.join(appPath, 'node_modules', 'react-native', 'scripts', packagerEnvFilename);
387 // ensure we overwrite file by passing the 'w' flag
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}