diff --git a/config/loaders/wxml-loader.js b/config/loaders/wxml-loader.js index 0407721..8814a7b 100644 --- a/config/loaders/wxml-loader.js +++ b/config/loaders/wxml-loader.js @@ -5,6 +5,8 @@ const { DOMParser, XMLSerializer } = require('@xmldom/xmldom'); const { resolve, relative } = require('path'); const { isUrlRequest, urlToRequest } = require('loader-utils'); +const fs = require('fs'); +const path = require('path'); const BOOLEAN_ATTRS = [ 'wx:else', @@ -82,7 +84,9 @@ const CURRENT_LOCALE_KEY = '$_locale'; const LOCALE_CHANGE_HANDLER_NAME = '$_localeChange'; const COMMON_LOCALE_DATA = '$_common_translations'; const CURRENT_LOCALE_DATA = '$_translations'; -const WXS_PATH = 'i18n/locales.wxs'; + +const DEFAULT_WXS_FILENAME = 'locales.wxs'; +const WXS_PATH = 'i18n' + '/' +DEFAULT_WXS_FILENAME; function existsT(str) { if (!str) return false; @@ -94,10 +98,25 @@ function existsT(str) { ); } +function replaceDoubleSlash(str) { + return str.replace(/\\/g, '/'); +} + function replaceT(str) { return str.replace(/t\(/g, 'i18n.t('); } +function getWxsCode() { + const BASE_PATH = path.dirname( + require.resolve( + `${process.cwd()}/node_modules/oak-frontend-base/src/platforms/wechatMp/i18n/wxs/wxs.js` + ) + ); + const code = fs.readFileSync(path.join(BASE_PATH, '/wxs.js'), 'utf-8'); + const runner = `module.exports = { \nt: Interpreter.getMessageInterpreter() \n}`; + return [code, runner].join('\n'); +} + module.exports = async function (content) { // loader的缓存功能 // this.cacheable && this.cacheable(); @@ -109,21 +128,24 @@ module.exports = async function (content) { options: webpackLegacyOptions, _module = {}, _compilation = {}, + _compiler = {}, resourcePath, } = this; + const { output } = _compiler.options; + const { path: outputPath } = output; const { context, target } = webpackLegacyOptions || this; const issuer = _compilation.moduleGraph.getIssuer(this._module); const issuerContext = (issuer && issuer.context) || context; const root = resolve(context, issuerContext); let source = content; - let relativePath; // locales.wxs相对路径 + let wxsRelativePath; // locales.wxs相对路径 //判断是否存在i18n的t函数 if (existsT(source)) { //判断加载的xml是否为本项目自身的文件 const isSelf = context.indexOf(projectContext) !== -1; if (isSelf) { //本项目xml - relativePath = relative( + wxsRelativePath = relative( context, projectContext + '/' + WXS_PATH ).replace(/\\/g, '/'); @@ -132,7 +154,7 @@ module.exports = async function (content) { const index = context.lastIndexOf(WeChatMpDir); if (index !== -1) { const p = context.substring(index + WeChatMpDir.length); - relativePath = relative( + wxsRelativePath = relative( projectContext + p, projectContext + '/' + WXS_PATH ).replace(/\\/g, '/'); @@ -140,7 +162,7 @@ module.exports = async function (content) { } source = - `` + + `` + source; } // 注入全局message组件 @@ -174,10 +196,13 @@ module.exports = async function (content) { !isDynamicSrc(value) && isUrlRequest(value, root) ) { - if (relativePath === value) { - // 如果出现直接读取项目下i18n/locales.wxs - const path = projectContext + '/' + WXS_PATH; - requests.push(path); + if (wxsRelativePath === value) { + // dist目录下生成一个i18n/locales.wxs文件 + const path = resolve(outputPath, WXS_PATH); + if (!fs.existsSync(replaceDoubleSlash(path))) { + const wxsContent = `${getWxsCode()}`; + this.emitFile(WXS_PATH, wxsContent); + } } else { const path = resolve(root, value); // const request = urlToRequest(value, root); diff --git a/config/mp/paths.js b/config/mp/paths.js index 495e766..11548b0 100644 --- a/config/mp/paths.js +++ b/config/mp/paths.js @@ -37,6 +37,7 @@ module.exports = { dotenv: resolveApp('.env'), appPath: resolveApp('.'), appBuild: resolveApp(buildPath), + appIndexDevJs: resolveModule(resolveApp, 'src/app.dev'), appIndexJs: resolveModule(resolveApp, 'src/app'), appPackageJson: resolveRoot('package.json'), appSrc: resolveApp('src'), @@ -48,6 +49,7 @@ module.exports = { appTsBuildInfoFile: resolveRoot('node_modules/.cache/tsconfig.tsbuildinfo'), publicUrlOrPath: '/', appOutSrc: resolveRoot('src'), + oakConfigJson: resolveApp('src/oak.config.json'), }; diff --git a/config/mp/webpack.config.js b/config/mp/webpack.config.js index 33d11a6..a326888 100644 --- a/config/mp/webpack.config.js +++ b/config/mp/webpack.config.js @@ -14,41 +14,10 @@ const getClientEnvironment = require('./env'); const paths = require('./paths'); const env = getClientEnvironment(); - -const isEnvDevelopment = env.raw.NODE_ENV === 'development'; -const isEnvProduction = env.raw.NODE_ENV === 'production'; const pkg = require(paths.appPackageJson); // process.env.OAK_PLATFORM: wechatMp | wechatPublic | web | node -const relativeFileLoader = (ext = '[ext]') => { - return { - loader: 'file-loader', - options: { - useRelativePath: true, - name: `[path][name].${ext}`, - context: paths.appSrc, - }, - }; -}; - -const oakFileLoader = (ext = '[ext]') => { - return { - loader: 'file-loader', - options: { - useRelativePath: true, - name: `[path][name].${ext}`, - outputPath: (url, resourcePath, context) => { - const outputPath = url.split( - 'oak-general-business/wechatMp/' - )[1]; - return outputPath; - }, - context: paths.appSrc, - }, - }; -}; - const copyPatterns = [].concat(pkg.copyWebpack || []).map((pattern) => typeof pattern === 'string' ? { @@ -57,203 +26,237 @@ const copyPatterns = [].concat(pkg.copyWebpack || []).map((pattern) => } : pattern ); -const oakReg = /oak-general-business\/wechatMp|oak-general-business\\wechatMp/; +const oakRegex = /oak-general-business\/wechatMp|oak-general-business\\wechatMp/; -module.exports = { - context: paths.appSrc, - devtool: isEnvDevelopment ? 'source-map' : false, - mode: isEnvProduction ? 'production' : isEnvDevelopment && 'development', - target: 'web', - entry: { - app: paths.appIndexJs, - }, - output: { - path: paths.appBuild, - filename: '[name].js', - publicPath: paths.publicUrlOrPath, - globalObject: 'global', - }, - resolve: { - alias: { - '@': paths.appSrc, - assert: require.resolve('assert'), +module.exports = function (webpackEnv) { + const isEnvDevelopment = webpackEnv === 'development'; + const isEnvProduction = webpackEnv === 'production'; + + const relativeFileLoader = (ext = '[ext]') => { + return { + loader: 'file-loader', + options: { + useRelativePath: true, + name: `[path][name].${ext}`, + context: paths.appSrc, + }, + }; + }; + + const oakFileLoader = (ext = '[ext]') => { + return { + loader: 'file-loader', + options: { + useRelativePath: true, + name: `[path][name].${ext}`, + outputPath: (url, resourcePath, context) => { + const outputPath = url.split( + 'oak-general-business/wechatMp/' + )[1]; + return outputPath; + }, + context: paths.appSrc, + }, + }; + }; + + return { + context: paths.appSrc, + devtool: isEnvDevelopment ? 'source-map' : false, + mode: isEnvProduction + ? 'production' + : isEnvDevelopment && 'development', + target: 'web', + entry: { + app: isEnvProduction ? paths.appIndexJs : paths.appIndexDevJs, }, - extensions: paths.moduleFileExtensions.map((ext) => `.${ext}`), - symlinks: true, - fallback: { - crypto: require.resolve('crypto-browserify'), - buffer: require.resolve('safe-buffer'), - stream: require.resolve('stream-browserify'), + output: { + path: paths.appBuild, + filename: '[name].js', + publicPath: paths.publicUrlOrPath, + globalObject: 'global', }, - }, - resolveLoader: { - // 第一种使用别名的方式引入自定义的loader - alias: { - 'wxml-loader': path.resolve(__dirname, '../loaders/wxml-loader.js'), + resolve: { + alias: { + '@': paths.appSrc, + assert: require.resolve('assert'), + }, + extensions: paths.moduleFileExtensions.map((ext) => `.${ext}`), + symlinks: true, + fallback: { + crypto: require.resolve('crypto-browserify'), + buffer: require.resolve('safe-buffer'), + stream: require.resolve('stream-browserify'), + }, }, - // 第二种方式选查找自己的loaders文件中有没有这个loader再查找node_modules文件 - // modules: [path.resolve(__dirname, 'loaders'), 'node_modules'], - }, - optimization: { - // 标记未被使用的代码 - usedExports: true, - // 删除 usedExports 标记的未使用的代码 - minimize: isEnvProduction, - minimizer: [ - new TerserPlugin({ - extractComments: false, - }), - ], - }, - module: { - rules: [ - { - test: /\.wxs$/, - include: /src/, - type: 'javascript/auto', - use: [relativeFileLoader()], + resolveLoader: { + // 第一种使用别名的方式引入自定义的loader + alias: { + 'wxml-loader': path.resolve( + __dirname, + '../loaders/wxml-loader.js' + ), }, - { - test: /\.wxs$/, - include: oakReg, - type: 'javascript/auto', - use: [relativeFileLoader()], - }, - { - test: /\.(png|jpg|gif|svg)$/, - include: /src/, - type: 'javascript/auto', - use: relativeFileLoader(), - }, - { - test: /\.(png|jpg|gif|svg)$/, - include: oakReg, - type: 'javascript/auto', - use: oakFileLoader(), - }, - { - test: /\.less$/, - include: /src/, - exclude: /node_modules/, - use: [ - relativeFileLoader('wxss'), - { - loader: 'less-loader', - }, - ], - }, - { - test: /\.less$/, - include: oakReg, - type: 'javascript/auto', - use: [ - oakFileLoader('wxss'), - { - loader: 'less-loader', - options: { - lessOptions: () => { - const oakConfigJson = require(`${paths.appSrc}/oak.config.json`); - return { - javascriptEnabled: true, - modifyVars: oakConfigJson.theme, - }; + // 第二种方式选查找自己的loaders文件中有没有这个loader再查找node_modules文件 + // modules: [path.resolve(__dirname, 'loaders'), 'node_modules'], + }, + optimization: { + // 标记未被使用的代码 + usedExports: true, + // 删除 usedExports 标记的未使用的代码 + minimize: isEnvProduction, + minimizer: [ + new TerserPlugin({ + extractComments: false, + }), + ], + }, + module: { + rules: [ + { + test: /\.wxs$/, + include: /src/, + type: 'javascript/auto', + use: [relativeFileLoader()], + }, + { + test: /\.wxs$/, + include: oakRegex, + type: 'javascript/auto', + use: [relativeFileLoader()], + }, + { + test: /\.(png|jpg|gif|svg)$/, + include: /src/, + type: 'javascript/auto', + use: relativeFileLoader(), + }, + { + test: /\.(png|jpg|gif|svg)$/, + include: oakRegex, + type: 'javascript/auto', + use: oakFileLoader(), + }, + { + test: /\.less$/, + include: /src/, + exclude: /node_modules/, + use: [ + relativeFileLoader('wxss'), + { + loader: 'less-loader', + }, + ], + }, + { + test: /\.less$/, + include: oakRegex, + type: 'javascript/auto', + use: [ + oakFileLoader('wxss'), + { + loader: 'less-loader', + options: { + lessOptions: () => { + const oakConfigJson = require(paths.oakConfigJson); + return { + javascriptEnabled: true, + modifyVars: oakConfigJson.theme, + }; + }, }, }, - }, - ], - }, - { - test: /\.js$/, - exclude: /node_modules/, - loader: 'babel-loader', - }, - { - test: /\.ts$/, - exclude: /node_modules/, - loader: 'ts-loader', - }, - // { - // test: /\.json$/, - // include: /src/, - // exclude: /node_modules/, - // type: 'asset/resource', - // generator: { - // filename: `[path][name].[ext]`, - // }, - // // type: 'javascript/auto', - // // use: [relativeFileLoader('json')], - // }, - { - test: /\.(xml|wxml)$/, - include: /src/, - // type: 'asset/resource', - // generator: { - // filename: `[path][name].[ext]`, + ], + }, + { + test: /\.js$/, + exclude: /node_modules/, + loader: 'babel-loader', + }, + { + test: /\.ts$/, + exclude: /node_modules/, + loader: 'ts-loader', + }, + // { + // test: /\.json$/, + // include: /src/, + // exclude: /node_modules/, + // type: 'asset/resource', + // generator: { + // filename: `[path][name].[ext]`, + // }, + // // type: 'javascript/auto', + // // use: [relativeFileLoader('json')], // }, - type: 'javascript/auto', - use: [ - relativeFileLoader('wxml'), - { - loader: 'wxml-loader', - options: { - context: paths.appSrc, + { + test: /\.(xml|wxml)$/, + include: /src/, + type: 'javascript/auto', + use: [ + relativeFileLoader('wxml'), + { + loader: 'wxml-loader', + options: { + context: paths.appSrc, + }, }, - }, - ], - }, - { - test: /\.(xml|wxml)$/, - include: oakReg, - type: 'javascript/auto', - use: [ - oakFileLoader('wxml'), - { - loader: 'wxml-loader', - options: { - context: paths.appSrc, + ], + }, + { + test: /\.(xml|wxml)$/, + include: oakRegex, + type: 'javascript/auto', + use: [ + oakFileLoader('wxml'), + { + loader: 'wxml-loader', + options: { + context: paths.appSrc, + }, }, - }, - ], - }, - ], - }, - plugins: [ - new UiExtractPlugin({ context: paths.appSrc }), - new OakWeChatMpPlugin({ - exclude: ['*/weui-miniprogram/*'], - include: ['project.config.json', 'sitemap.json'], - split: !isEnvDevelopment, - }), - new webpack.DefinePlugin(env.stringified), - new StylelintPlugin({ - fix: true, - files: '**/*.(sa|sc|le|wx|c)ss', - }), - new ProgressBarPlugin({ - summary: false, - format: ':msg :percent (:elapsed seconds)', - customSummary: (buildTime) => - console.log( - chalk.gray(`\n[${new Date().toLocaleDateString()}`), - chalk.green(`Compiled successfully!(${buildTime})\n`) - ), - }), - new webpack.ProvidePlugin({ - Buffer: ['buffer', 'Buffer'], - }), - ].concat( - copyPatterns.length > 0 - ? [ - new CopyWebpackPlugin({ - patterns: copyPatterns, - }), - ] - : [] - ), - watch: true, - watchOptions: { - aggregateTimeout: 600, - ignored: '**/node_modules', - followSymlinks: true, - }, + ], + }, + ], + }, + plugins: [ + new UiExtractPlugin({ context: paths.appSrc }), + new OakWeChatMpPlugin({ + exclude: ['*/weui-miniprogram/*'], + include: ['project.config.json', 'sitemap.json'], + split: !isEnvDevelopment, + }), + new webpack.DefinePlugin(env.stringified), + new StylelintPlugin({ + fix: true, + files: '**/*.(sa|sc|le|wx|c)ss', + }), + new ProgressBarPlugin({ + summary: false, + format: ':msg :percent (:elapsed seconds)', + customSummary: (buildTime) => + console.log( + chalk.gray(`\n[${new Date().toLocaleDateString()}`), + chalk.green(`Compiled successfully!(${buildTime})\n`) + ), + }), + new webpack.ProvidePlugin({ + Buffer: ['buffer', 'Buffer'], + }), + ].concat( + copyPatterns.length > 0 + ? [ + new CopyWebpackPlugin({ + patterns: copyPatterns, + }), + ] + : [] + ), + watch: true, + watchOptions: { + aggregateTimeout: 600, + ignored: '**/node_modules', + followSymlinks: true, + }, + }; }; diff --git a/config/web/paths.js b/config/web/paths.js index 7ddaffc..c0f368a 100644 --- a/config/web/paths.js +++ b/config/web/paths.js @@ -58,6 +58,7 @@ module.exports = { appBuild: resolveApp(buildPath), appPublic: resolveApp('public'), appHtml: resolveApp('public/index.html'), + appIndexDevJs: resolveModule(resolveApp, 'src/index.dev'), appIndexJs: resolveModule(resolveApp, 'src/index'), appPackageJson: resolveRoot('package.json'), appSrc: resolveApp('src'), diff --git a/lib/build.js b/lib/build.js index 8f2f37b..2ec7ade 100644 --- a/lib/build.js +++ b/lib/build.js @@ -7,12 +7,16 @@ const tip_style_1 = require("./tip-style"); const cross_spawn_1 = __importDefault(require("cross-spawn")); async function build(cmd) { if (!cmd.target) { - (0, tip_style_1.Error)(`${(0, tip_style_1.error)(`Please add --target web or --target mp to he command`)}`); + (0, tip_style_1.Error)(`${(0, tip_style_1.error)(`Please add --target web or --target mp or --target wechatMp to he command`)}`); return; } (0, tip_style_1.Success)(`${(0, tip_style_1.success)(`build ${cmd.target} environment: ${cmd.mode}`)}`); - if (cmd.target === 'mp') { - const result = cross_spawn_1.default.sync(`cross-env NODE_ENV=${cmd.mode} NODE_TARGET=${cmd.target} "${process.execPath}"`, [require.resolve('../scripts/build-mp.js')], { + if (cmd.target === 'mp' || cmd.target === 'wechatMp') { + const result = cross_spawn_1.default.sync(`cross-env NODE_ENV=${cmd.mode} NODE_TARGET=${cmd.target} "${process.execPath}"`, [ + require.resolve(`../scripts/${cmd.mode === 'production' + ? 'build-mp.js' + : 'start-mp.js'}`), + ], { stdio: 'inherit', shell: true, }); diff --git a/lib/file-handle.d.ts b/lib/file-handle.d.ts index 50447af..0413e16 100644 --- a/lib/file-handle.d.ts +++ b/lib/file-handle.d.ts @@ -1,5 +1,4 @@ /// -/// import { PathLike } from 'fs'; import { checkFileExistsAndCreateType } from './enum'; /** diff --git a/scripts/build-mp.js b/scripts/build-mp.js index dfef3e2..d09acb1 100644 --- a/scripts/build-mp.js +++ b/scripts/build-mp.js @@ -2,13 +2,14 @@ require('../config/mp/env'); const webpack = require('webpack'); const chalk = require('chalk'); -const webpackConfig = require('../config/mp/webpack.config'); +const configFactory = require('../config/mp/webpack.config'); +const config = configFactory('production'); const paths = require('../config/mp/paths'); const getClientEnvironment = require('../config/mp/env'); const env = getClientEnvironment(); -webpack(webpackConfig, (err, stats) => { +webpack(config, (err, stats) => { if (err) { console.log(chalk.red(err.stack || err)); if (err.details) { diff --git a/scripts/start-mp.js b/scripts/start-mp.js new file mode 100644 index 0000000..8e24fc6 --- /dev/null +++ b/scripts/start-mp.js @@ -0,0 +1,29 @@ +require('../config/mp/env'); + +const webpack = require('webpack'); +const chalk = require('chalk'); +const configFactory = require('../config/mp/webpack.config'); +const config = configFactory('development'); + +const paths = require('../config/mp/paths'); +const getClientEnvironment = require('../config/mp/env'); +const env = getClientEnvironment(); + +webpack(config, (err, stats) => { + if (err) { + console.log(chalk.red(err.stack || err)); + if (err.details) { + console.log(chalk.red(err.details)); + } + return undefined; + } + if (stats) { + const info = stats.toJson(); + if (stats.hasErrors()) { + info.errors.forEach((ele) => console.warn(ele)); + } + if (stats.hasWarnings()) { + info.warnings.forEach((ele) => console.warn(ele)); + } + } +}); diff --git a/src/build.ts b/src/build.ts index 3144bf2..e397456 100644 --- a/src/build.ts +++ b/src/build.ts @@ -12,15 +12,25 @@ import spawn from 'cross-spawn'; export default async function build(cmd: any) { if (!cmd.target) { Error( - `${error(`Please add --target web or --target mp to he command`)}` + `${error( + `Please add --target web or --target mp or --target wechatMp to he command` + )}` ); return; } Success(`${success(`build ${cmd.target} environment: ${cmd.mode}`)}`); - if (cmd.target === 'mp') { + if (cmd.target === 'mp' || cmd.target === 'wechatMp') { const result = spawn.sync( `cross-env NODE_ENV=${cmd.mode} NODE_TARGET=${cmd.target} "${process.execPath}"`, - [require.resolve('../scripts/build-mp.js')], + [ + require.resolve( + `../scripts/${ + cmd.mode === 'production' + ? 'build-mp.js' + : 'start-mp.js' + }` + ), + ], { stdio: 'inherit', shell: true,