From 5091c4a730dbf4cae17ddfcf4f041f15cc4c5518 Mon Sep 17 00:00:00 2001 From: Xc Date: Sun, 26 Nov 2023 22:43:00 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=E5=A4=A7=E9=87=8F?= =?UTF-8?q?=E5=92=8Cnative=E7=9B=B8=E5=85=B3=E7=9A=84=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/babel-plugin/oakRender.js | 274 +------------------------------ config/native/metro.config.js | 39 +++++ config/native/transformer.js | 108 ++++++++++++ config/utils/injectGetRender.js | 194 ++++++++++++++++++++++ lib/build.js | 42 ++++- lib/index.js | 8 +- lib/makeDomain.js | 2 +- lib/makeLocale.js | 2 +- lib/run.js | 46 ++++-- package.json | 3 + src/build.ts | 49 +++++- src/index.ts | 9 +- src/makeDomain.ts | 2 +- src/makeLocale.ts | 2 +- src/run.ts | 64 +++++--- 15 files changed, 517 insertions(+), 327 deletions(-) create mode 100644 config/native/metro.config.js create mode 100644 config/native/transformer.js create mode 100644 config/utils/injectGetRender.js diff --git a/config/babel-plugin/oakRender.js b/config/babel-plugin/oakRender.js index fbe6507..f724f3d 100644 --- a/config/babel-plugin/oakRender.js +++ b/config/babel-plugin/oakRender.js @@ -1,275 +1,17 @@ -const fs = require('fs'); -const { relative, resolve } = require('path'); const t = require('@babel/types'); -const assert = require('assert'); +const { parse } = require('path'); +const { injectGetRender } = require('../utils/injectGetRender'); module.exports = (babel) => { return { visitor: { - Program(path, state) { + CallExpression(path, state) { const { cwd, filename } = state; - const rel = relative(cwd, filename).replace(/\\/g, '/'); - const tsPage = /(pages|components|namespaces)[\\/][\w|\W]+[\\/]index\.ts$/.test(rel); - const jsPage = /(pages|components|namespaces)[\\/][\w|\W]+[\\/]index\.js$/.test(rel); - if (tsPage || jsPage) { - const tsxFile = filename.replace( - /index\.(ts|js)$/, - tsPage ? 'web.tsx' : 'web.jsx' - ); - const jsFile = filename.replace( - /index\.(ts|js)$/, - 'web.js' - ); - const xmlFile = filename.replace( - /index\.(ts|js)$/, - 'index.xml' - ); - const xmlFileExists = fs.existsSync(xmlFile); - - const tsxFileExists = fs.existsSync(tsxFile); - const jsFileExists = fs.existsSync(jsFile); - const pcTsxFile = filename.replace( - /index\.(ts|js)$/, - tsPage ? 'web.pc.tsx' : 'web.pc.jsx' - ); - const pcJsFile = filename.replace( - /index\.(ts|js)$/, - 'web.pc.js' - ); - const pcTsxFileExists = fs.existsSync(pcTsxFile); - const pcJsFileExists = fs.existsSync(pcJsFile); - - - /** 根据tsx文件存在的情况,注入如下的render代码 - * if (this.props.width === 'xs') { - const renderMobile = require('./web.tsx').default; - return renderMobile.call(this); - } - else { - const renderScreen = require('./web.pc.tsx').default; - return renderScreen.call(this); - } - */ - const renderTsxStatements = [ - t.variableDeclaration('const', [ - t.variableDeclarator( - t.identifier('render'), - t.memberExpression( - t.callExpression(t.identifier('require'), [ - t.stringLiteral( - `./web.${tsPage ? 'tsx' : 'jsx'}` - ), - ]), - t.identifier('default') - ) - ), - ]), - t.returnStatement( - t.identifier('render') - ), - ]; - const renderJsStatements = [ - t.variableDeclaration('const', [ - t.variableDeclarator( - t.identifier('render'), - t.memberExpression( - t.callExpression(t.identifier('require'), [ - t.stringLiteral('./web.js'), - ]), - t.identifier('default') - ) - ), - ]), - t.returnStatement( - t.identifier('render') - ), - ]; - const renderPcTsxStatements = [ - t.variableDeclaration('const', [ - t.variableDeclarator( - t.identifier('render'), - t.memberExpression( - t.callExpression(t.identifier('require'), [ - t.stringLiteral( - `./web.pc.${tsPage ? 'tsx' : 'jsx'}` - ), - ]), - t.identifier('default') - ) - ), - ]), - t.returnStatement( - t.identifier('render') - ), - ]; - const renderPcJsStatements = [ - t.variableDeclaration('const', [ - t.variableDeclarator( - t.identifier('render'), - t.memberExpression( - t.callExpression(t.identifier('require'), [ - t.stringLiteral('./web.pc.js'), - ]), - t.identifier('default') - ) - ), - ]), - t.returnStatement( - t.identifier('render') - ), - ]; - const getStatements = () => { - const statements = []; - if (tsxFileExists && pcTsxFileExists) { - statements.push( - t.ifStatement( - t.binaryExpression( - '===', - t.memberExpression( - t.memberExpression( - t.thisExpression(), - t.identifier('props') - ), - t.identifier('width') - ), - t.stringLiteral('xs') - ), - t.blockStatement(renderTsxStatements), - t.blockStatement(renderPcTsxStatements) - ) - ); - } else if (jsFileExists && pcJsFileExists) { - statements.push( - t.ifStatement( - t.binaryExpression( - '===', - t.memberExpression( - t.memberExpression( - t.thisExpression(), - t.identifier('props') - ), - t.identifier('width') - ), - t.stringLiteral('xs') - ), - t.blockStatement(renderJsStatements), - t.blockStatement(renderPcJsStatements) - ) - ); - } else if (jsFileExists && pcTsxFileExists) { - statements.push( - t.ifStatement( - t.binaryExpression( - '===', - t.memberExpression( - t.memberExpression( - t.thisExpression(), - t.identifier('props') - ), - t.identifier('width') - ), - t.stringLiteral('xs') - ), - t.blockStatement(renderJsStatements), - t.blockStatement(renderPcTsxStatements) - ) - ); - } else if (tsxFileExists && pcJsFileExists) { - statements.push( - t.ifStatement( - t.binaryExpression( - '===', - t.memberExpression( - t.memberExpression( - t.thisExpression(), - t.identifier('props') - ), - t.identifier('width') - ), - t.stringLiteral('xs') - ), - t.blockStatement(renderTsxStatements), - t.blockStatement(renderPcJsStatements) - ) - ); - } else if (tsxFileExists) { - statements.push(...renderTsxStatements); - } else if (pcTsxFileExists) { - statements.push(...renderPcTsxStatements); - } else if (jsFileExists) { - statements.push(...renderJsStatements); - } else if (pcJsFileExists) { - statements.push(...renderPcJsStatements); - } else if (!xmlFileExists) { - assert( - false, - `${filename}文件中不存在web.tsx或者web.pc.tsx` - ); - } - return statements; - }; - const node = path.node; - const body = node.body; - body.forEach((node2) => { - // export default OakComponent({}) - if (t.isExportDefaultDeclaration(node2)) { - let node3 = node2.declaration; - if (node3) { - if (t.isTSAsExpression(node3)) { - // export default OakComponent({}) as .... - node3 = node3.expression; - } - } - - if (t.isCallExpression(node3) && node3.callee.name === 'OakComponent') { - const statements = getStatements(); - node3.arguments.forEach((node4) => { - if (t.isObjectExpression(node4)) { - const propertyRender = t.objectProperty( - t.identifier('getRender'), - t.functionExpression( - null, - [], - t.blockStatement(statements) - ) - ); - node4.properties.unshift(propertyRender); - } - else { - assert(false, `[${filename}]OakComponent调用参数不是ObjectExpression`); - } - }); - } - } - // exports.default = OakPage({})、exports.default = OakComponent({}) - else if ( - t.isExpressionStatement(node2) && - t.isAssignmentExpression(node2.expression) && - t.isCallExpression(node2.expression.right) && - t.isIdentifier(node2.expression.right.callee) && - node2.expression.right.callee.name === - 'OakComponent' - ) { - const statements = getStatements(); - node2.expression.right.arguments.forEach( - (node3) => { - if (t.isObjectExpression(node3)) { - const propertyRender = t.objectProperty( - t.identifier('getRender'), - t.functionExpression( - null, - [], - t.blockStatement(statements) - ) - ); - node3.properties.unshift( - propertyRender - ); - } - } - ); - } - }); + const { base } = parse(filename); + const node = path.node; + if (['index.ts', 'index.js'].includes(base) && t.isCallExpression(node) && t.isIdentifier(node.callee) && node.callee.name === 'OakComponent') { + console.log(filename, base); + injectGetRender(node, cwd, filename, 'web'); } }, }, diff --git a/config/native/metro.config.js b/config/native/metro.config.js new file mode 100644 index 0000000..3f67473 --- /dev/null +++ b/config/native/metro.config.js @@ -0,0 +1,39 @@ + +const { resolve } = require('path'); +const watchFolders = process.env.NODE_ENV === 'production' ? ['../src', '../node_modules'] : [ + '../src', '../node_modules', '../../oak-domain', '../../oak-common-aspect', '../../oak-external-sdk', + '../../oak-frontend-base', '../../oak-general-business', '../../oak-memory-tree-store' +]; + +const sourceExts = (process.env.NODE_ENV === 'production' || process.env.PROD === 'true') ? + ['prod.ts', 'ts', 'tsx', 'prod.js', 'js', 'jsx', 'less', 'json'] : + ['dev.ts', 'ts', 'tsx', 'dev.js', 'js', 'jsx', 'less', 'json']; + +const NullModules = ['fs', 'url']; +/** + * Metro configuration + * https://facebook.github.io/metro/docs/configuration + * + * @type {import('metro-config').MetroConfig} + */ +const config = { + transformer: { + babelTransformerPath: resolve(__dirname, 'transformer.js'), + // hermesParser: true, + }, + resolver: { + sourceExts, + resolveRequest: (context, moduleName, platform) => { + if (NullModules.includes(moduleName)) { + return { + type: 'empty', + }; + } + return context.resolveRequest(context, moduleName, platform); + }, + nodeModulesPaths: [resolve(process.cwd(), '..', 'node_modules')], // development模式下,oak的库是以文件方式链接,其自身的node_modules里可能会缺失一些库 + }, + watchFolders, +}; + +module.exports = config; diff --git a/config/native/transformer.js b/config/native/transformer.js new file mode 100644 index 0000000..d5281f9 --- /dev/null +++ b/config/native/transformer.js @@ -0,0 +1,108 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * + * @format + * @oncall react_native + */ + +"use strict"; + +const traverse = require('@babel/traverse').default; +const t = require('@babel/types'); +const path = require('path'); +const less = require('less'); +const css2rn = require("css-to-react-native-transform").default; +const { parseSync, transformFromAstSync, transformSync: babelTransform } = require("@babel/core"); +const nullthrows = require("nullthrows"); + +const { injectGetRender } = require('../utils/injectGetRender'); + +async function renderToCSS({ src, filename, options = {} }) { + const { lessOptions = {} } = options; + const { css } = await less.render(src, { paths: [path.dirname(filename)], ...lessOptions }); + return css; +} + +function renderCSSToReactNative(css) { + return css2rn(css, { parseMediaQueries: true }); +} + +function transform({ filename, options, plugins, src }) { + const OLD_BABEL_ENV = process.env.BABEL_ENV; + process.env.BABEL_ENV = options.dev + ? "development" + : process.env.BABEL_ENV || "production"; + try { + const babelConfig = { + caller: { + name: "oak", + bundler: "oak", + platform: options.platform, + }, + ast: true, + babelrc: options.enableBabelRCLookup, + code: false, + cwd: options.projectRoot, + highlightCode: true, + filename, + plugins, + sourceType: "module", + // NOTE(EvanBacon): We split the parse/transform steps up to accommodate + // Hermes parsing, but this defaults to cloning the AST which increases + // the transformation time by a fair amount. + // You get this behavior by default when using Babel's `transform` method directly. + cloneInputAst: false, + }; + + const transInner = (src) => { + const sourceAst = options.hermesParser + ? require("hermes-parser").parse(src, { + babel: true, + sourceType: babelConfig.sourceType, + }) + : parseSync(src, babelConfig); + + const transformResult = transformFromAstSync(sourceAst, src, babelConfig); + + // 为page和componet下的OakComponent注入getRender函数,去取得同目录下的render.native.tsx + const resultAst = transformResult.ast; + const { base } = path.parse(filename); + if (['index.ts', 'index.js'].includes(base)) { + traverse(resultAst, { + CallExpression(path) { + const node = path.node; + if (t.isIdentifier(node.callee) && node.callee.name === 'OakComponent') { + injectGetRender(node, options.projectRoot, filename, 'native'); + } + } + }) + } + // injectGetRender(transformResult.ast.program.body, path.resolve(process.cwd(), '..'), filename, 'native'); + return { + ast: nullthrows(transformResult.ast), + metadata: transformResult.metadata, + }; + }; + + if (filename.endsWith('less')) { + return renderToCSS({ src, filename, options }).then((css) => { + const cssObject = renderCSSToReactNative(css); + const newSrc = `module.exports = ${JSON.stringify(cssObject)}`; + return transInner(newSrc); + }); + } + + return transInner(src); + } finally { + if (OLD_BABEL_ENV) { + process.env.BABEL_ENV = OLD_BABEL_ENV; + } + } +} +module.exports = { + transform, +}; diff --git a/config/utils/injectGetRender.js b/config/utils/injectGetRender.js new file mode 100644 index 0000000..97fe130 --- /dev/null +++ b/config/utils/injectGetRender.js @@ -0,0 +1,194 @@ +const { relative, dirname, join } = require('path'); +const fs = require('fs'); +const t = require('@babel/types'); +const traverse = require('@babel/traverse').default; +const assert = require('assert'); + +/** + * + * @param {*} node + * @param {*} projectRoot + * @param {*} filename + * @param {*} env 'web' | 'native' + */ +function injectGetRender(node, projectRoot, filename, env) { + assert(t.isCallExpression(node) && t.isIdentifier(node.callee) && node.callee.name === 'OakComponent'); + const dir = dirname(filename); + if (env === 'web') { + // web要根据this.props.width的宽度决定注入web.tsx还是web.pc.tsx + const tsxFile = join(dir, 'web.tsx'); + const jsFile = join(dir, 'web.js'); + const jsxFile = join(dir, 'web.jsx'); + + let webDestFile; + if (fs.existsSync(tsxFile)) { + webDestFile = './web.tsx'; + } + else if (fs.existsSync(jsFile)) { + webDestFile = './web.js'; + } + else if (fs.existsSync(jsxFile)) { + webDestFile = './web.jsx'; + } + const acquireWebFileStmt = webDestFile && t.variableDeclaration('const', [ + t.variableDeclarator( + t.identifier('oakRenderFn'), + t.memberExpression( + t.callExpression(t.identifier('require'), [ + t.stringLiteral(webDestFile), + ]), + t.identifier('default') + ) + ), + ]); + + let pcDestFile; + const pcTsxFile = join(dir, 'web.pc.tsx'); + const pcJsFile = join(dir, 'web.pc.js'); + const pcJsxFile = join(dir, 'web.pc.jsx'); + if (fs.existsSync(pcTsxFile)) { + pcDestFile = './web.pc.tsx'; + } + else if (fs.existsSync(pcJsFile)) { + pcDestFile = './web.pc.js'; + } + else if (fs.existsSync(pcJsxFile)) { + pcDestFile = './web.pc.jsx'; + } + const acquirePcFileStmt = pcDestFile && t.variableDeclaration('const', [ + t.variableDeclarator( + t.identifier('oakRenderFn'), + t.memberExpression( + t.callExpression(t.identifier('require'), [ + t.stringLiteral(pcDestFile), + ]), + t.identifier('default') + ) + ), + ]); + + const getStatements = () => { + /** 根据tsx文件存在的情况,注入如下的getRender函数 + * if (this.props.width === 'xs') { + const oakRenderFn = require('./web.tsx').default; + return oakRenderFn; + } + else { + const oakRenderFn = require('./web.pc.tsx').default; + return oakRenderFn; + } + */ + const statements = []; + if (acquirePcFileStmt && acquireWebFileStmt) { + statements.push( + t.ifStatement( + t.binaryExpression( + '===', + t.memberExpression( + t.memberExpression( + t.thisExpression(), + t.identifier('props') + ), + t.identifier('width') + ), + t.stringLiteral('xs') + ), + t.blockStatement([ + acquireWebFileStmt, + t.returnStatement( + t.identifier('oakRenderFn') + ) + ]), + t.blockStatement([ + acquirePcFileStmt, + t.returnStatement( + t.identifier('oakRenderFn') + ) + ]) + ) + ); + } + else if (acquirePcFileStmt) { + statements.push( + acquirePcFileStmt, + t.returnStatement( + t.identifier('oakRenderFn') + ) + ); + } + else if (acquireWebFileStmt) { + statements.push( + acquireWebFileStmt, + t.returnStatement( + t.identifier('oakRenderFn') + ) + ); + } else { + assert( + false, + `${dir}文件夹中不存在web.tsx或者web.pc.tsx,无法渲染` + ); + } + return statements; + }; + const statements = getStatements(); + node.arguments.forEach((node4) => { + if (t.isObjectExpression(node4)) { + const propertyRender = t.objectProperty( + t.identifier('getRender'), + t.functionExpression( + null, + [], + t.blockStatement(statements) + ) + ); + node4.properties.unshift(propertyRender); + } + else { + assert(false, `[${filename}]OakComponent调用参数不是ObjectExpression`); + } + }); + } + else { + assert(env === 'native'); + /** native不用检测(react-native会自动检测render.native或render.ios/android),直接注入 + * OakComponent({ + * getRender() { + * const oakRenderFn = require('./render').default; + * return oakRenderFn; + * }, + * }) + */ + const arg = node.arguments[0]; + assert(t.isObjectExpression(arg)); + const propertyRender = t.objectProperty( + t.identifier('getRender'), + t.functionExpression(null, [], t.blockStatement( + [ + t.variableDeclaration('var', [ + t.variableDeclarator( + t.identifier('oakRenderFn'), + t.memberExpression( + t.callExpression( + t.identifier('require'), + [ + t.stringLiteral('./render') + ] + ), + t.identifier('default') + ), + ) + ]), + t.returnStatement( + t.identifier('oakRenderFn') + ) + ] + )) + ); + arg.properties.unshift(propertyRender); + } +} + +module.exports = { + injectGetRender +}; \ No newline at end of file diff --git a/lib/build.js b/lib/build.js index d2f62ed..1e5a00d 100644 --- a/lib/build.js +++ b/lib/build.js @@ -3,10 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const tip_style_1 = require("./tip-style"); const cross_spawn_1 = tslib_1.__importDefault(require("cross-spawn")); +const path_1 = require("path"); const makeLocale_1 = tslib_1.__importDefault(require("./makeLocale")); +const fs_1 = require("fs"); async function build(cmd) { if (!cmd.target) { - (0, tip_style_1.Error)(`${(0, tip_style_1.error)(`Please add --target web or --target mp or --target wechatMp to he command`)}`); + (0, tip_style_1.Error)(`${(0, tip_style_1.error)(`Please add --target web or --target mp(wechatMp) or --target rn(native) to run the project in Web/WechatMp/ReactNative environment`)}`); return; } // 先makeLocale @@ -19,7 +21,7 @@ async function build(cmd) { : ''} ${cmd.target !== 'web' && cmd.mode !== 'production' ? `split:${!!cmd.split}` : ''}`)}`); - if (cmd.target === 'mp' || cmd.target === 'wechatMp') { + if (['mp', 'wechatMp'].includes(cmd.target)) { const result = cross_spawn_1.default.sync(`cross-env`, [ `NODE_ENV=${cmd.mode}`, `NODE_TARGET=${cmd.target}`, @@ -72,5 +74,41 @@ async function build(cmd) { (0, tip_style_1.Error)(`${(0, tip_style_1.error)(`执行失败`)}`); } } + else if (['native', 'rn'].includes(cmd.target)) { + const prjDir = process.cwd(); + const cwd = (0, path_1.resolve)(prjDir, cmd.subDir || 'native'); + (0, fs_1.copyFileSync)((0, path_1.resolve)(prjDir, 'package.json'), (0, path_1.resolve)(cwd, 'package.json')); + // rn不支持注入NODE_ENVIRONMENT这样的环境变量,cross-env没有用 + /* const result = spawn.sync( + 'react-native', + [ + 'start', + ], + { + cwd, + stdio: 'inherit', + shell: true, + } + ); */ + const result = cross_spawn_1.default.sync(`cross-env`, [ + `NODE_ENV=${cmd.mode}`, + 'OAK_PLATFORM=native', + 'react-native', + 'start' + ].filter(Boolean), { + cwd, + stdio: 'inherit', + shell: true, + }); + if (result.status === 0) { + (0, tip_style_1.Success)(`${(0, tip_style_1.success)(`执行完成`)}`); + } + else { + (0, tip_style_1.Error)(`${(0, tip_style_1.error)(`执行失败`)}`); + } + } + else { + (0, tip_style_1.Error)(`${(0, tip_style_1.error)(`target could only be web or mp(wechatMp) or rn(native)`)}`); + } } exports.default = build; diff --git a/lib/index.js b/lib/index.js index 72a97a7..4e0f1d6 100755 --- a/lib/index.js +++ b/lib/index.js @@ -23,7 +23,7 @@ function enhanceErrorMessages(methodName, log) { this.outputHelp(); console.log(` ` + (0, tip_style_1.error)(log(...args))); console.log(); - process.exit(1); + process.exit(-1); }; } const currentNodeVersion = process.versions.node; @@ -38,7 +38,7 @@ if (Number(major) < minNodeVersion) { minNodeVersion + ' or higher. \n' + 'Please update your version of Node.'); - process.exit(1); + process.exit(-1); } commander_1.default.version(config_1.CLI_VERSION, '-v, --version').usage(' [options]'); commander_1.default @@ -87,8 +87,8 @@ commander_1.default .action(create_1.update); commander_1.default .command('run') - .option('-i, --initialize', 'true') - .option('-m, --mode ', 'mode') + .option('-p, --platform ', 'platform') + .option('-d, --subDir ', 'subDirName') .description(`run backend server by ${config_1.CLI_NAME}`) .action(run_1.default); // output help information on unknown commands diff --git a/lib/makeDomain.js b/lib/makeDomain.js index 29b49ed..0890647 100644 --- a/lib/makeDomain.js +++ b/lib/makeDomain.js @@ -16,7 +16,7 @@ async function make() { } else { (0, tip_style_1.Error)(`${(0, tip_style_1.error)(`make 执行失败`)}`); - process.exit(1); + process.exit(-1); } } exports.default = make; diff --git a/lib/makeLocale.js b/lib/makeLocale.js index 3056c89..97c0de3 100644 --- a/lib/makeLocale.js +++ b/lib/makeLocale.js @@ -21,7 +21,7 @@ async function make(cmd, watch) { } else { (0, tip_style_1.Error)(`${(0, tip_style_1.error)(`make 执行失败`)}`); - process.exit(1); + process.exit(-1); } } else { diff --git a/lib/run.js b/lib/run.js index 96e0983..f9ca694 100644 --- a/lib/run.js +++ b/lib/run.js @@ -3,36 +3,46 @@ Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const tip_style_1 = require("./tip-style"); const cross_spawn_1 = tslib_1.__importDefault(require("cross-spawn")); +const path_1 = require("path"); +const fs_1 = require("fs"); async function run(options) { - if (options.initialize) { - (0, tip_style_1.Success)(`${(0, tip_style_1.success)('初始化数据库中……')}`); - // ts-node scripts/build-app-domain & npm link ./app-domain - const drop = options.args.includes('drop') || false; - const result = cross_spawn_1.default.sync('ts-node', [require.resolve('../scripts/' + 'initialize-server.ts'), `${drop}`], { + const prjDir = process.cwd(); + const cwd = (0, path_1.resolve)(process.cwd(), options.subDir || 'native'); + if (options.platform === 'ios') { + (0, fs_1.copyFileSync)((0, path_1.resolve)(prjDir, 'package.json'), (0, path_1.resolve)(cwd, 'package.json')); + (0, tip_style_1.Success)(`${(0, tip_style_1.primary)('run react-native run-ios')}`); + const result = cross_spawn_1.default.sync('react-native', ['run-ios'], { + cwd, stdio: 'inherit', shell: true, }); if (result.status === 0) { - (0, tip_style_1.Success)(`${(0, tip_style_1.success)(`初始化数据库完成`)}`); + (0, tip_style_1.Success)(`${(0, tip_style_1.success)(`react-native run-ios success`)}`); } else { - Error(`${(0, tip_style_1.error)(`初始化数据库失败`)}`); - process.exit(1); + Error(`${(0, tip_style_1.error)('react-native run-ios fail')}`); + process.exit(-1); } } - else { - (0, tip_style_1.Success)(`${(0, tip_style_1.success)('启动服务器……')}`); - console.log(options.mode); - // ts-node scripts/build-app-domain & npm link ./app-domain - const result = cross_spawn_1.default.sync(`cross-env`, [ - `NODE_ENV=${options.mode}`, - 'OAK_PLATFORM=server', - 'ts-node', - require.resolve('../scripts/' + 'start-server.js'), - ], { + else if (options.platform === 'android') { + (0, tip_style_1.Success)(`${(0, tip_style_1.primary)('run react-native run-android')}`); + (0, fs_1.copyFileSync)((0, path_1.resolve)(prjDir, 'package.json'), (0, path_1.resolve)(cwd, 'package.json')); + const result = cross_spawn_1.default.sync('react-native', ['run-android'], { + cwd, stdio: 'inherit', shell: true, }); + if (result.status === 0) { + (0, tip_style_1.Success)(`${(0, tip_style_1.success)(`react-native run-android success`)}`); + } + else { + Error(`${(0, tip_style_1.error)('react-native run-android fail')}`); + process.exit(-1); + } + } + else { + Error((0, tip_style_1.error)(`unrecoganized platfrom: ${options.platform}`)); + process.exit(-1); } } exports.default = run; diff --git a/package.json b/package.json index 18c1799..74ce5d8 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "@types/node": "^20.6.0", "@types/shelljs": "^0.8.8", "@types/uuid": "^8.3.4", + "babel-plugin-module-resolver": "^5.0.0", "fork-ts-checker-webpack-plugin": "^8.0.0", "node-watch": "^0.7.4", "querystring-es3": "^0.2.1", @@ -68,6 +69,7 @@ "css": "^3.0.0", "css-loader": "^6.5.1", "css-minimizer-webpack-plugin": "^3.2.0", + "css-to-react-native-transform": "^2.0.0", "dotenv": "^10.0.0", "dotenv-expand": "^5.1.0", "dotenv-webpack": "^7.1.0", @@ -106,6 +108,7 @@ "prompts": "^2.4.2", "react-app-polyfill": "^3.0.0", "react-dev-utils": "^12.0.1", + "react-native": "^0.72.7", "react-refresh": "^0.11.0", "required-path": "^1.0.1", "resolve": "^1.20.0", diff --git a/src/build.ts b/src/build.ts index 28f5668..019b009 100644 --- a/src/build.ts +++ b/src/build.ts @@ -8,13 +8,15 @@ import { Warn, } from './tip-style'; import spawn from 'cross-spawn'; +import { resolve } from 'path'; import makeLocale from './makeLocale'; +import { copyFileSync } from 'fs'; export default async function build(cmd: any) { if (!cmd.target) { Error( `${error( - `Please add --target web or --target mp or --target wechatMp to he command` + `Please add --target web or --target mp(wechatMp) or --target rn(native) to run the project in Web/WechatMp/ReactNative environment` )}` ); return; @@ -37,7 +39,7 @@ export default async function build(cmd: any) { }` )}` ); - if (cmd.target === 'mp' || cmd.target === 'wechatMp') { + if (['mp', 'wechatMp'].includes(cmd.target)) { const result = spawn.sync( `cross-env`, [ @@ -103,4 +105,47 @@ export default async function build(cmd: any) { Error(`${error(`执行失败`)}`); } } + else if (['native', 'rn'].includes(cmd.target)) { + const prjDir = process.cwd(); + const cwd = resolve(prjDir, cmd.subDir || 'native'); + copyFileSync(resolve(prjDir, 'package.json'), resolve(cwd, 'package.json')); + // rn不支持注入NODE_ENVIRONMENT这样的环境变量,cross-env没有用 + /* const result = spawn.sync( + 'react-native', + [ + 'start', + ], + { + cwd, + stdio: 'inherit', + shell: true, + } + ); */ + const result = spawn.sync( + `cross-env`, + [ + `NODE_ENV=${cmd.mode}`, + 'OAK_PLATFORM=native', + 'react-native', + 'start' + ].filter(Boolean), + { + cwd, + stdio: 'inherit', + shell: true, + } + ); + if (result.status === 0) { + Success(`${success(`执行完成`)}`); + } else { + Error(`${error(`执行失败`)}`); + } + } + else { + Error( + `${error( + `target could only be web or mp(wechatMp) or rn(native)` + )}` + ); + } } diff --git a/src/index.ts b/src/index.ts index b137fc8..2ae6b8e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -21,7 +21,7 @@ function enhanceErrorMessages(methodName: string, log: Function) { this.outputHelp(); console.log(` ` + error(log(...args))); console.log(); - process.exit(1); + process.exit(-1); }; } @@ -40,7 +40,7 @@ if (Number(major) < minNodeVersion) { ' or higher. \n' + 'Please update your version of Node.' ); - process.exit(1); + process.exit(-1); } program.version(CLI_VERSION, '-v, --version').usage(' [options]'); @@ -93,10 +93,11 @@ program .action(update); program .command('run') - .option('-i, --initialize', 'true') - .option('-m, --mode ', 'mode') + .option('-p, --platform ', 'platform') + .option('-d, --subDir ', 'subDirName') .description(`run backend server by ${CLI_NAME}`) .action(run); + // output help information on unknown commands program.arguments('').action((cmd) => { program.outputHelp(); diff --git a/src/makeDomain.ts b/src/makeDomain.ts index da797d2..5c109eb 100644 --- a/src/makeDomain.ts +++ b/src/makeDomain.ts @@ -26,6 +26,6 @@ export default async function make() { Success(`${success(`make 执行完成`)}`); } else { Error(`${error(`make 执行失败`)}`); - process.exit(1); + process.exit(-1); } } diff --git a/src/makeLocale.ts b/src/makeLocale.ts index 84cc3a6..5be92d0 100644 --- a/src/makeLocale.ts +++ b/src/makeLocale.ts @@ -31,7 +31,7 @@ export default async function make(cmd: any, watch?: boolean) { Success(`${success(`make 执行完成`)}`); } else { Error(`${error(`make 执行失败`)}`); - process.exit(1); + process.exit(-1); } } else { diff --git a/src/run.ts b/src/run.ts index 0ddc2a5..43714cb 100644 --- a/src/run.ts +++ b/src/run.ts @@ -7,44 +7,54 @@ import { Warn, } from './tip-style'; import spawn from 'cross-spawn'; +import { resolve } from 'path'; +import { copyFileSync, unlinkSync } from 'fs'; -export default async function run(options: any): Promise { - if (options.initialize) { - Success(`${success('初始化数据库中……')}`); - // ts-node scripts/build-app-domain & npm link ./app-domain - const drop = options.args.includes('drop') || false; +export default async function run(options: any): Promise { + const prjDir = process.cwd(); + const cwd = resolve(process.cwd(), options.subDir || 'native'); + if (options.platform === 'ios') { + copyFileSync(resolve(prjDir, 'package.json'), resolve(cwd, 'package.json')); + Success(`${primary('run react-native run-ios')}`); const result = spawn.sync( - 'ts-node', - [require.resolve('../scripts/' + 'initialize-server.ts'), `${drop}`], + 'react-native', + ['run-ios'], { + cwd, stdio: 'inherit', shell: true, } ); - + if (result.status === 0) { - Success(`${success(`初始化数据库完成`)}`); + Success(`${success(`react-native run-ios success`)}`); } else { - Error(`${error(`初始化数据库失败`)}`); - process.exit(1); + Error(`${error('react-native run-ios fail')}`); + process.exit(-1); + } + } + else if (options.platform === 'android') { + Success(`${primary('run react-native run-android')}`); + copyFileSync(resolve(prjDir, 'package.json'), resolve(cwd, 'package.json')); + const result = spawn.sync( + 'react-native', + ['run-android'], + { + cwd, + stdio: 'inherit', + shell: true, + } + ); + + if (result.status === 0) { + Success(`${success(`react-native run-android success`)}`); + } else { + Error(`${error('react-native run-android fail')}`); + process.exit(-1); } } else { - Success(`${success('启动服务器……')}`); - console.log(options.mode); - // ts-node scripts/build-app-domain & npm link ./app-domain - const result = spawn.sync( - `cross-env`, - [ - `NODE_ENV=${options.mode}`, - 'OAK_PLATFORM=server', - 'ts-node', - require.resolve('../scripts/' + 'start-server.js'), - ], - { - stdio: 'inherit', - shell: true, - } - ); + Error(error(`unrecoganized platfrom: ${options.platform}`)); + process.exit(-1); } } \ No newline at end of file