locales.wxs 动态写入dist

This commit is contained in:
Wang Kejun 2022-06-25 17:27:35 +08:00
parent 72f01718a5
commit 0b52dde7e3
9 changed files with 312 additions and 238 deletions

View File

@ -5,6 +5,8 @@
const { DOMParser, XMLSerializer } = require('@xmldom/xmldom'); const { DOMParser, XMLSerializer } = require('@xmldom/xmldom');
const { resolve, relative } = require('path'); const { resolve, relative } = require('path');
const { isUrlRequest, urlToRequest } = require('loader-utils'); const { isUrlRequest, urlToRequest } = require('loader-utils');
const fs = require('fs');
const path = require('path');
const BOOLEAN_ATTRS = [ const BOOLEAN_ATTRS = [
'wx:else', 'wx:else',
@ -82,7 +84,9 @@ const CURRENT_LOCALE_KEY = '$_locale';
const LOCALE_CHANGE_HANDLER_NAME = '$_localeChange'; const LOCALE_CHANGE_HANDLER_NAME = '$_localeChange';
const COMMON_LOCALE_DATA = '$_common_translations'; const COMMON_LOCALE_DATA = '$_common_translations';
const CURRENT_LOCALE_DATA = '$_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) { function existsT(str) {
if (!str) return false; if (!str) return false;
@ -94,10 +98,25 @@ function existsT(str) {
); );
} }
function replaceDoubleSlash(str) {
return str.replace(/\\/g, '/');
}
function replaceT(str) { function replaceT(str) {
return str.replace(/t\(/g, 'i18n.t('); 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) { module.exports = async function (content) {
// loader的缓存功能 // loader的缓存功能
// this.cacheable && this.cacheable(); // this.cacheable && this.cacheable();
@ -109,21 +128,24 @@ module.exports = async function (content) {
options: webpackLegacyOptions, options: webpackLegacyOptions,
_module = {}, _module = {},
_compilation = {}, _compilation = {},
_compiler = {},
resourcePath, resourcePath,
} = this; } = this;
const { output } = _compiler.options;
const { path: outputPath } = output;
const { context, target } = webpackLegacyOptions || this; const { context, target } = webpackLegacyOptions || this;
const issuer = _compilation.moduleGraph.getIssuer(this._module); const issuer = _compilation.moduleGraph.getIssuer(this._module);
const issuerContext = (issuer && issuer.context) || context; const issuerContext = (issuer && issuer.context) || context;
const root = resolve(context, issuerContext); const root = resolve(context, issuerContext);
let source = content; let source = content;
let relativePath; // locales.wxs相对路径 let wxsRelativePath; // locales.wxs相对路径
//判断是否存在i18n的t函数 //判断是否存在i18n的t函数
if (existsT(source)) { if (existsT(source)) {
//判断加载的xml是否为本项目自身的文件 //判断加载的xml是否为本项目自身的文件
const isSelf = context.indexOf(projectContext) !== -1; const isSelf = context.indexOf(projectContext) !== -1;
if (isSelf) { if (isSelf) {
//本项目xml //本项目xml
relativePath = relative( wxsRelativePath = relative(
context, context,
projectContext + '/' + WXS_PATH projectContext + '/' + WXS_PATH
).replace(/\\/g, '/'); ).replace(/\\/g, '/');
@ -132,7 +154,7 @@ module.exports = async function (content) {
const index = context.lastIndexOf(WeChatMpDir); const index = context.lastIndexOf(WeChatMpDir);
if (index !== -1) { if (index !== -1) {
const p = context.substring(index + WeChatMpDir.length); const p = context.substring(index + WeChatMpDir.length);
relativePath = relative( wxsRelativePath = relative(
projectContext + p, projectContext + p,
projectContext + '/' + WXS_PATH projectContext + '/' + WXS_PATH
).replace(/\\/g, '/'); ).replace(/\\/g, '/');
@ -140,7 +162,7 @@ module.exports = async function (content) {
} }
source = source =
`<wxs src='${relativePath}' module='${I18nModuleName}'></wxs>` + `<wxs src='${wxsRelativePath}' module='${I18nModuleName}'></wxs>` +
source; source;
} }
// 注入全局message组件 // 注入全局message组件
@ -174,10 +196,13 @@ module.exports = async function (content) {
!isDynamicSrc(value) && !isDynamicSrc(value) &&
isUrlRequest(value, root) isUrlRequest(value, root)
) { ) {
if (relativePath === value) { if (wxsRelativePath === value) {
// 如果出现直接读取项目下i18n/locales.wxs // dist目录下生成一个i18n/locales.wxs文件
const path = projectContext + '/' + WXS_PATH; const path = resolve(outputPath, WXS_PATH);
requests.push(path); if (!fs.existsSync(replaceDoubleSlash(path))) {
const wxsContent = `${getWxsCode()}`;
this.emitFile(WXS_PATH, wxsContent);
}
} else { } else {
const path = resolve(root, value); const path = resolve(root, value);
// const request = urlToRequest(value, root); // const request = urlToRequest(value, root);

View File

@ -37,6 +37,7 @@ module.exports = {
dotenv: resolveApp('.env'), dotenv: resolveApp('.env'),
appPath: resolveApp('.'), appPath: resolveApp('.'),
appBuild: resolveApp(buildPath), appBuild: resolveApp(buildPath),
appIndexDevJs: resolveModule(resolveApp, 'src/app.dev'),
appIndexJs: resolveModule(resolveApp, 'src/app'), appIndexJs: resolveModule(resolveApp, 'src/app'),
appPackageJson: resolveRoot('package.json'), appPackageJson: resolveRoot('package.json'),
appSrc: resolveApp('src'), appSrc: resolveApp('src'),
@ -48,6 +49,7 @@ module.exports = {
appTsBuildInfoFile: resolveRoot('node_modules/.cache/tsconfig.tsbuildinfo'), appTsBuildInfoFile: resolveRoot('node_modules/.cache/tsconfig.tsbuildinfo'),
publicUrlOrPath: '/', publicUrlOrPath: '/',
appOutSrc: resolveRoot('src'), appOutSrc: resolveRoot('src'),
oakConfigJson: resolveApp('src/oak.config.json'),
}; };

View File

@ -14,41 +14,10 @@ const getClientEnvironment = require('./env');
const paths = require('./paths'); const paths = require('./paths');
const env = getClientEnvironment(); const env = getClientEnvironment();
const isEnvDevelopment = env.raw.NODE_ENV === 'development';
const isEnvProduction = env.raw.NODE_ENV === 'production';
const pkg = require(paths.appPackageJson); const pkg = require(paths.appPackageJson);
// process.env.OAK_PLATFORM: wechatMp | wechatPublic | web | node // 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) => const copyPatterns = [].concat(pkg.copyWebpack || []).map((pattern) =>
typeof pattern === 'string' typeof pattern === 'string'
? { ? {
@ -57,203 +26,237 @@ const copyPatterns = [].concat(pkg.copyWebpack || []).map((pattern) =>
} }
: pattern : pattern
); );
const oakReg = /oak-general-business\/wechatMp|oak-general-business\\wechatMp/; const oakRegex = /oak-general-business\/wechatMp|oak-general-business\\wechatMp/;
module.exports = { module.exports = function (webpackEnv) {
context: paths.appSrc, const isEnvDevelopment = webpackEnv === 'development';
devtool: isEnvDevelopment ? 'source-map' : false, const isEnvProduction = webpackEnv === 'production';
mode: isEnvProduction ? 'production' : isEnvDevelopment && 'development',
target: 'web', const relativeFileLoader = (ext = '[ext]') => {
entry: { return {
app: paths.appIndexJs, loader: 'file-loader',
}, options: {
output: { useRelativePath: true,
path: paths.appBuild, name: `[path][name].${ext}`,
filename: '[name].js', context: paths.appSrc,
publicPath: paths.publicUrlOrPath, },
globalObject: 'global', };
}, };
resolve: {
alias: { const oakFileLoader = (ext = '[ext]') => {
'@': paths.appSrc, return {
assert: require.resolve('assert'), 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}`), output: {
symlinks: true, path: paths.appBuild,
fallback: { filename: '[name].js',
crypto: require.resolve('crypto-browserify'), publicPath: paths.publicUrlOrPath,
buffer: require.resolve('safe-buffer'), globalObject: 'global',
stream: require.resolve('stream-browserify'),
}, },
}, resolve: {
resolveLoader: { alias: {
// 第一种使用别名的方式引入自定义的loader '@': paths.appSrc,
alias: { assert: require.resolve('assert'),
'wxml-loader': path.resolve(__dirname, '../loaders/wxml-loader.js'), },
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文件 resolveLoader: {
// modules: [path.resolve(__dirname, 'loaders'), 'node_modules'], // 第一种使用别名的方式引入自定义的loader
}, alias: {
optimization: { 'wxml-loader': path.resolve(
// 标记未被使用的代码 __dirname,
usedExports: true, '../loaders/wxml-loader.js'
// 删除 usedExports 标记的未使用的代码 ),
minimize: isEnvProduction,
minimizer: [
new TerserPlugin({
extractComments: false,
}),
],
},
module: {
rules: [
{
test: /\.wxs$/,
include: /src/,
type: 'javascript/auto',
use: [relativeFileLoader()],
}, },
{ // 第二种方式选查找自己的loaders文件中有没有这个loader再查找node_modules文件
test: /\.wxs$/, // modules: [path.resolve(__dirname, 'loaders'), 'node_modules'],
include: oakReg, },
type: 'javascript/auto', optimization: {
use: [relativeFileLoader()], // 标记未被使用的代码
}, usedExports: true,
{ // 删除 usedExports 标记的未使用的代码
test: /\.(png|jpg|gif|svg)$/, minimize: isEnvProduction,
include: /src/, minimizer: [
type: 'javascript/auto', new TerserPlugin({
use: relativeFileLoader(), extractComments: false,
}, }),
{ ],
test: /\.(png|jpg|gif|svg)$/, },
include: oakReg, module: {
type: 'javascript/auto', rules: [
use: oakFileLoader(), {
}, test: /\.wxs$/,
{ include: /src/,
test: /\.less$/, type: 'javascript/auto',
include: /src/, use: [relativeFileLoader()],
exclude: /node_modules/, },
use: [ {
relativeFileLoader('wxss'), test: /\.wxs$/,
{ include: oakRegex,
loader: 'less-loader', type: 'javascript/auto',
}, use: [relativeFileLoader()],
], },
}, {
{ test: /\.(png|jpg|gif|svg)$/,
test: /\.less$/, include: /src/,
include: oakReg, type: 'javascript/auto',
type: 'javascript/auto', use: relativeFileLoader(),
use: [ },
oakFileLoader('wxss'), {
{ test: /\.(png|jpg|gif|svg)$/,
loader: 'less-loader', include: oakRegex,
options: { type: 'javascript/auto',
lessOptions: () => { use: oakFileLoader(),
const oakConfigJson = require(`${paths.appSrc}/oak.config.json`); },
return { {
javascriptEnabled: true, test: /\.less$/,
modifyVars: oakConfigJson.theme, 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$/,
test: /\.js$/, exclude: /node_modules/,
exclude: /node_modules/, loader: 'babel-loader',
loader: 'babel-loader', },
}, {
{ test: /\.ts$/,
test: /\.ts$/, exclude: /node_modules/,
exclude: /node_modules/, loader: 'ts-loader',
loader: 'ts-loader', },
}, // {
// { // test: /\.json$/,
// test: /\.json$/, // include: /src/,
// include: /src/, // exclude: /node_modules/,
// exclude: /node_modules/, // type: 'asset/resource',
// type: 'asset/resource', // generator: {
// generator: { // filename: `[path][name].[ext]`,
// filename: `[path][name].[ext]`, // },
// }, // // type: 'javascript/auto',
// // type: 'javascript/auto', // // use: [relativeFileLoader('json')],
// // use: [relativeFileLoader('json')],
// },
{
test: /\.(xml|wxml)$/,
include: /src/,
// type: 'asset/resource',
// generator: {
// filename: `[path][name].[ext]`,
// }, // },
type: 'javascript/auto', {
use: [ test: /\.(xml|wxml)$/,
relativeFileLoader('wxml'), include: /src/,
{ type: 'javascript/auto',
loader: 'wxml-loader', use: [
options: { relativeFileLoader('wxml'),
context: paths.appSrc, {
loader: 'wxml-loader',
options: {
context: paths.appSrc,
},
}, },
}, ],
], },
}, {
{ test: /\.(xml|wxml)$/,
test: /\.(xml|wxml)$/, include: oakRegex,
include: oakReg, type: 'javascript/auto',
type: 'javascript/auto', use: [
use: [ oakFileLoader('wxml'),
oakFileLoader('wxml'), {
{ loader: 'wxml-loader',
loader: 'wxml-loader', options: {
options: { context: paths.appSrc,
context: paths.appSrc, },
}, },
}, ],
], },
}, ],
], },
}, plugins: [
plugins: [ new UiExtractPlugin({ context: paths.appSrc }),
new UiExtractPlugin({ context: paths.appSrc }), new OakWeChatMpPlugin({
new OakWeChatMpPlugin({ exclude: ['*/weui-miniprogram/*'],
exclude: ['*/weui-miniprogram/*'], include: ['project.config.json', 'sitemap.json'],
include: ['project.config.json', 'sitemap.json'], split: !isEnvDevelopment,
split: !isEnvDevelopment, }),
}), new webpack.DefinePlugin(env.stringified),
new webpack.DefinePlugin(env.stringified), new StylelintPlugin({
new StylelintPlugin({ fix: true,
fix: true, files: '**/*.(sa|sc|le|wx|c)ss',
files: '**/*.(sa|sc|le|wx|c)ss', }),
}), new ProgressBarPlugin({
new ProgressBarPlugin({ summary: false,
summary: false, format: ':msg :percent (:elapsed seconds)',
format: ':msg :percent (:elapsed seconds)', customSummary: (buildTime) =>
customSummary: (buildTime) => console.log(
console.log( chalk.gray(`\n[${new Date().toLocaleDateString()}`),
chalk.gray(`\n[${new Date().toLocaleDateString()}`), chalk.green(`Compiled successfully!(${buildTime})\n`)
chalk.green(`Compiled successfully!(${buildTime})\n`) ),
), }),
}), new webpack.ProvidePlugin({
new webpack.ProvidePlugin({ Buffer: ['buffer', 'Buffer'],
Buffer: ['buffer', 'Buffer'], }),
}), ].concat(
].concat( copyPatterns.length > 0
copyPatterns.length > 0 ? [
? [ new CopyWebpackPlugin({
new CopyWebpackPlugin({ patterns: copyPatterns,
patterns: copyPatterns, }),
}), ]
] : []
: [] ),
), watch: true,
watch: true, watchOptions: {
watchOptions: { aggregateTimeout: 600,
aggregateTimeout: 600, ignored: '**/node_modules',
ignored: '**/node_modules', followSymlinks: true,
followSymlinks: true, },
}, };
}; };

View File

@ -58,6 +58,7 @@ module.exports = {
appBuild: resolveApp(buildPath), appBuild: resolveApp(buildPath),
appPublic: resolveApp('public'), appPublic: resolveApp('public'),
appHtml: resolveApp('public/index.html'), appHtml: resolveApp('public/index.html'),
appIndexDevJs: resolveModule(resolveApp, 'src/index.dev'),
appIndexJs: resolveModule(resolveApp, 'src/index'), appIndexJs: resolveModule(resolveApp, 'src/index'),
appPackageJson: resolveRoot('package.json'), appPackageJson: resolveRoot('package.json'),
appSrc: resolveApp('src'), appSrc: resolveApp('src'),

View File

@ -7,12 +7,16 @@ const tip_style_1 = require("./tip-style");
const cross_spawn_1 = __importDefault(require("cross-spawn")); const cross_spawn_1 = __importDefault(require("cross-spawn"));
async function build(cmd) { async function build(cmd) {
if (!cmd.target) { 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; return;
} }
(0, tip_style_1.Success)(`${(0, tip_style_1.success)(`build ${cmd.target} environment: ${cmd.mode}`)}`); (0, tip_style_1.Success)(`${(0, tip_style_1.success)(`build ${cmd.target} environment: ${cmd.mode}`)}`);
if (cmd.target === 'mp') { 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/build-mp.js')], { 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', stdio: 'inherit',
shell: true, shell: true,
}); });

View File

@ -1,5 +1,4 @@
/// <reference types="node" /> /// <reference types="node" />
/// <reference types="node" />
import { PathLike } from 'fs'; import { PathLike } from 'fs';
import { checkFileExistsAndCreateType } from './enum'; import { checkFileExistsAndCreateType } from './enum';
/** /**

View File

@ -2,13 +2,14 @@ require('../config/mp/env');
const webpack = require('webpack'); const webpack = require('webpack');
const chalk = require('chalk'); 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 paths = require('../config/mp/paths');
const getClientEnvironment = require('../config/mp/env'); const getClientEnvironment = require('../config/mp/env');
const env = getClientEnvironment(); const env = getClientEnvironment();
webpack(webpackConfig, (err, stats) => { webpack(config, (err, stats) => {
if (err) { if (err) {
console.log(chalk.red(err.stack || err)); console.log(chalk.red(err.stack || err));
if (err.details) { if (err.details) {

29
scripts/start-mp.js Normal file
View File

@ -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));
}
}
});

View File

@ -12,15 +12,25 @@ import spawn from 'cross-spawn';
export default async function build(cmd: any) { export default async function build(cmd: any) {
if (!cmd.target) { if (!cmd.target) {
Error( 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; return;
} }
Success(`${success(`build ${cmd.target} environment: ${cmd.mode}`)}`); Success(`${success(`build ${cmd.target} environment: ${cmd.mode}`)}`);
if (cmd.target === 'mp') { if (cmd.target === 'mp' || cmd.target === 'wechatMp') {
const result = spawn.sync( const result = spawn.sync(
`cross-env NODE_ENV=${cmd.mode} NODE_TARGET=${cmd.target} "${process.execPath}"`, `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', stdio: 'inherit',
shell: true, shell: true,