处理了小程序对xml中实现i18n的编译器部分支持
This commit is contained in:
parent
2d80616117
commit
0435138517
|
|
@ -104,7 +104,7 @@ module.exports = (babel) => {
|
|||
return {
|
||||
visitor: {
|
||||
CallExpression(path, state) {
|
||||
const { cwd, filename } = state;
|
||||
const { cwd, filename } = state;
|
||||
const res = resolve(cwd, filename).replace(/\\/g, '/');
|
||||
// this.props.t/this.t/t
|
||||
// 处理策略为给第二个参数中加上'#oakNameSpace, #oakModule两个参数,告知t模块此文件相应的位置,再加以处理寻找
|
||||
|
|
|
|||
|
|
@ -3,9 +3,10 @@
|
|||
* @param {*} content 文件信息
|
||||
*/
|
||||
const { DOMParser, XMLSerializer } = require('@xmldom/xmldom');
|
||||
const { resolve, relative } = require('path');
|
||||
const { resolve, relative, join } = require('path');
|
||||
const { isUrlRequest, urlToRequest } = require('loader-utils');
|
||||
const fs = require('fs');
|
||||
const assert = require('assert');
|
||||
const path = require('path');
|
||||
|
||||
const BOOLEAN_ATTRS = [
|
||||
|
|
@ -96,7 +97,7 @@ const LOCALE_CHANGE_HANDLER_NAME = '$_localeChange';
|
|||
const CURRENT_LOCALE_DATA = '$_translations';
|
||||
|
||||
const DEFAULT_WXS_FILENAME = 'locales.wxs';
|
||||
const WXS_PATH = 'i18n' + '/' +DEFAULT_WXS_FILENAME;
|
||||
const WXS_PATH = 'i18n' + '/' + DEFAULT_WXS_FILENAME;
|
||||
|
||||
function existsT(str) {
|
||||
if (!str) return false;
|
||||
|
|
@ -136,9 +137,91 @@ function getAppJson(context) {
|
|||
return JSON.parse(data);
|
||||
}
|
||||
|
||||
//////////
|
||||
const { parseSync, transformFromAstSync } = require('@babel/core');
|
||||
const t = require('@babel/types');
|
||||
const traverseAst = require('@babel/traverse').default;
|
||||
/**
|
||||
* 判断代码段中是否有t()
|
||||
* @param {*} text
|
||||
* @returns
|
||||
*/
|
||||
function codeChunkIncludesT(text) {
|
||||
return /{{(\w|\W)*\W*t\((\w|\W)+\)(\w|\W)*}}/g.test(text)
|
||||
}
|
||||
|
||||
/**
|
||||
* 改写代码段中的t()部分
|
||||
* @param {*} text
|
||||
* @param {*} namespace
|
||||
* @param {*} moduleName
|
||||
* @returns
|
||||
*/
|
||||
function transformCode(text, namespace, moduleName) {
|
||||
const t2 = text.replace(/{{((\w|\W)*)}}/g, '$1');
|
||||
const ast = parseSync(t2);
|
||||
traverseAst(ast, {
|
||||
enter(path) {
|
||||
if (path.isCallExpression()) {
|
||||
const { node } = path;
|
||||
if (t.isIdentifier(node.callee) && node.callee.name === 't') {
|
||||
const { arguments } = node;
|
||||
// 在t的后面加五个参数(oakLocales, oakLng, oakDefaultLng, oakNamespace, oakModule)
|
||||
arguments.push(
|
||||
t.identifier('oakLocales'),
|
||||
t.identifier('oakLng'),
|
||||
t.identifier('oakDefaultLng'),
|
||||
t.stringLiteral(namespace),
|
||||
t.stringLiteral(moduleName)
|
||||
);
|
||||
node.callee = t.memberExpression(
|
||||
t.identifier('i18n'),
|
||||
t.identifier('t')
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
const { code } = transformFromAstSync(ast);
|
||||
assert(code.endsWith(';'));
|
||||
return `{{${code.substring(0, code.length - 1)}}}`;
|
||||
}
|
||||
//////////
|
||||
|
||||
const ModuleNameDict = {};
|
||||
// 根据当前处理的文件路径推导出wxs目录相对应的路径
|
||||
function parseXmlFile(appRootPath, appRootSrcPath, appSrcPath, filePath) {
|
||||
// 目前所有的pages/components应当都位于appRootSrcPath下
|
||||
const isSelf = filePath.startsWith(appRootSrcPath);
|
||||
const filePath2 = filePath.replace(/\\/g, '/');
|
||||
|
||||
const fileProjectPath = filePath2.replace(/((\w|\W)*)(\/src|\/lib)(\/pages\/|\/components\/)((\w|\W)*)/g, '$1');
|
||||
let moduleName = ModuleNameDict[fileProjectPath];
|
||||
if (!moduleName) {
|
||||
const { name } = require(join(fileProjectPath, 'package.json'));
|
||||
moduleName = ModuleNameDict[fileProjectPath] = name;
|
||||
}
|
||||
|
||||
const relativePath = filePath2.replace(/(\w|\W)*(\/pages\/|\/components\/)((\w|\W)*)/g, '$3');
|
||||
assert(relativePath);
|
||||
const ns = `${moduleName}-${filePath.includes('pages') ? 'p' : 'c'}-${relativePath.replace(/\//g, '-')}`;
|
||||
|
||||
let level = relativePath.split('/').length + 1; // 加上pages的深度,未来根据isSelf还要进一步处理
|
||||
let wxsRelativePath = '';
|
||||
while (level-- > 0) {
|
||||
wxsRelativePath += '../';
|
||||
}
|
||||
wxsRelativePath += 'wxs';
|
||||
return {
|
||||
wxsRelativePath,
|
||||
ns,
|
||||
moduleName,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = async function (content) {
|
||||
const options = this.getOptions() || {}; //获取配置参数
|
||||
const { context: projectContext, cacheDirectory = true } = options; // context 本项目路径
|
||||
const { appSrcPath, appRootPath, appRootSrcPath, cacheDirectory = true } = options; // context 本项目路径
|
||||
// loader的缓存功能
|
||||
this.cacheable && this.cacheable(cacheDirectory);
|
||||
const callback = this.async();
|
||||
|
|
@ -151,29 +234,38 @@ module.exports = async function (content) {
|
|||
} = this;
|
||||
const { output, mode } = _compiler.options;
|
||||
const { path: outputPath } = output;
|
||||
const { context, target } = webpackLegacyOptions || this;
|
||||
const { context: filePath, target } = webpackLegacyOptions || this;
|
||||
const issuer = _compilation.moduleGraph.getIssuer(this._module);
|
||||
const issuerContext = (issuer && issuer.context) || context;
|
||||
const root = resolve(context, issuerContext);
|
||||
const issuerContext = (issuer && issuer.context) || filePath;
|
||||
const root = resolve(filePath, issuerContext);
|
||||
let source = content;
|
||||
let wxsRelativePath; // locales.wxs相对路径
|
||||
|
||||
const {
|
||||
wxsRelativePath,
|
||||
ns,
|
||||
moduleName,
|
||||
} = parseXmlFile(appRootPath, appRootSrcPath, appSrcPath, filePath);
|
||||
const i18nWxsFile = `${wxsRelativePath}/${I18nModuleName}.wxs`;
|
||||
|
||||
// 无条件注入i18n.wxs
|
||||
source = `<wxs src='${i18nWxsFile}' module='${I18nModuleName}'></wxs>` + source;
|
||||
//判断是否存在i18n的t函数
|
||||
if (existsT(source)) {
|
||||
/* if (existsT(source)) {
|
||||
//判断加载的xml是否为本项目自身的文件
|
||||
const isSelf = context.indexOf(projectContext) !== -1;
|
||||
const isSelf = filePath.indexOf(appSrcPath) !== -1;
|
||||
if (isSelf) {
|
||||
//本项目xml
|
||||
wxsRelativePath = relative(
|
||||
context,
|
||||
projectContext + '/' + WXS_PATH
|
||||
filePath,
|
||||
appSrcPath + '/' + WXS_PATH
|
||||
).replace(/\\/g, '/');
|
||||
} else {
|
||||
//第三方项目的xml
|
||||
if (oakRegex.test(context)) {
|
||||
const p = context.replace(oakRegex, '');
|
||||
if (oakRegex.test(filePath)) {
|
||||
const p = filePath.replace(oakRegex, '');
|
||||
wxsRelativePath = relative(
|
||||
projectContext + '/' + p,
|
||||
projectContext + '/' + WXS_PATH
|
||||
appSrcPath + '/' + p,
|
||||
appSrcPath + '/' + WXS_PATH
|
||||
).replace(/\\/g, '/');
|
||||
}
|
||||
}
|
||||
|
|
@ -183,10 +275,10 @@ module.exports = async function (content) {
|
|||
`<wxs src='${wxsRelativePath}' module='${I18nModuleName}'></wxs>` +
|
||||
source;
|
||||
}
|
||||
}
|
||||
} */
|
||||
// 注入全局message组件
|
||||
if (/pages/.test(context)) {
|
||||
const appJson = getAppJson(projectContext);
|
||||
if (/pages/.test(filePath)) {
|
||||
const appJson = getAppJson(appSrcPath);
|
||||
if (
|
||||
appJson &&
|
||||
appJson.usingComponents &&
|
||||
|
|
@ -220,8 +312,8 @@ module.exports = async function (content) {
|
|||
const requests = [];
|
||||
traverse(doc, (node) => {
|
||||
if (node.nodeType === node.ATTRIBUTE_NODE) {
|
||||
if (existsT(node.value)) {
|
||||
const newVal = formatI18nT(node.value, resourcePath);
|
||||
if (codeChunkIncludesT(node.value)) {
|
||||
const newVal = transformCode(node.value, ns, moduleName);
|
||||
node.value = newVal;
|
||||
}
|
||||
}
|
||||
|
|
@ -234,13 +326,13 @@ module.exports = async function (content) {
|
|||
!isDynamicSrc(value) &&
|
||||
isUrlRequest(value, root)
|
||||
) {
|
||||
if (wxsRelativePath === value) {
|
||||
if (i18nWxsFile === value) {
|
||||
// dist目录下生成一个i18n/locales.wxs文件
|
||||
const path = resolve(outputPath, WXS_PATH);
|
||||
/* 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);
|
||||
|
|
@ -250,8 +342,8 @@ module.exports = async function (content) {
|
|||
}
|
||||
}
|
||||
if (node.nodeType === node.TEXT_NODE) {
|
||||
if (existsT(node.nodeValue)) {
|
||||
const newVal = formatI18nT(node.nodeValue, resourcePath);
|
||||
if (codeChunkIncludesT(node.nodeValue)) {
|
||||
const newVal = transformCode(node.nodeValue, ns, moduleName);
|
||||
node.deleteData(0, node.nodeValue.length);
|
||||
node.insertData(0, newVal);
|
||||
}
|
||||
|
|
@ -282,48 +374,4 @@ module.exports = async function (content) {
|
|||
} catch (err) {
|
||||
callback(err, content);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function formatI18nT(value, resourcePath) {
|
||||
// 处理i18n 把t()转成i18n.t()
|
||||
const p = replaceDoubleSlash(resourcePath).replace(
|
||||
oakPagesOrComponentsRegex,
|
||||
''
|
||||
);
|
||||
const eP = p.substring(0, p.lastIndexOf('/'));
|
||||
const ns = eP
|
||||
.split('/')
|
||||
.filter((ele) => !!ele)
|
||||
.join('-');
|
||||
const val = replaceT(value); // {{i18n.t()}}
|
||||
const valArr = val.split('}}');
|
||||
let newVal = '';
|
||||
valArr.forEach((ele, index) => {
|
||||
if (existsT(ele)) {
|
||||
const head = ele.substring(0, ele.indexOf('i18n.t(') + 7);
|
||||
let argsStr = ele.substring(ele.indexOf('i18n.t(') + 7);
|
||||
argsStr = argsStr.substring(0, argsStr.indexOf(')'));
|
||||
const end = ele.substring(ele.indexOf(')'));
|
||||
const arguments = argsStr.split(',').filter((ele2) => !!ele2);
|
||||
arguments &&
|
||||
arguments.forEach((nodeVal, index) => {
|
||||
if (index === 0 && nodeVal.indexOf(':') === -1) {
|
||||
arguments.splice(index, 1, `'${ns}:' + ` + nodeVal);
|
||||
}
|
||||
});
|
||||
newVal +=
|
||||
head +
|
||||
arguments.join(',') +
|
||||
`,${CURRENT_LOCALE_KEY},${CURRENT_LOCALE_DATA} || ''` +
|
||||
end +
|
||||
'}}';
|
||||
} else if (ele && ele.indexOf('{{') !== -1) {
|
||||
newVal += ele + '}}';
|
||||
} else {
|
||||
newVal += ele;
|
||||
}
|
||||
});
|
||||
|
||||
return newVal;
|
||||
}
|
||||
};
|
||||
|
|
@ -265,7 +265,9 @@ module.exports = function (webpackEnv) {
|
|||
{
|
||||
loader: 'wxml-loader',
|
||||
options: {
|
||||
context: paths.appSrc,
|
||||
appSrcPath: paths.appSrc,
|
||||
appRootPath: paths.appRootPath,
|
||||
appRootSrcPath: paths.appRootSrc,
|
||||
cacheDirectory: false,
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@
|
|||
"main": "lib/index.js",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "tsc --watch"
|
||||
"dev": "tsc --watch",
|
||||
"test": "node test.js"
|
||||
},
|
||||
"bin": {
|
||||
"oak-cli": "lib/index.js"
|
||||
|
|
|
|||
|
|
@ -11,6 +11,12 @@ const EntryPlugin = require('webpack/lib/EntryPlugin');
|
|||
const ensurePosix = require('ensure-posix-path');
|
||||
const requiredPath = require('required-path');
|
||||
|
||||
const { parseSync, transformFromAstSync } = require('@babel/core');
|
||||
const t = require('@babel/types');
|
||||
const { readFileSync, writeFileSync, existsSync, mkdirSync } = require('fs');
|
||||
const assert = require('assert');
|
||||
const traverseAst = require('@babel/traverse').default;
|
||||
|
||||
const pluginName = 'OakWeChatMpPlugin';
|
||||
|
||||
function replaceDoubleSlash(str) {
|
||||
|
|
@ -72,6 +78,17 @@ class OakWeChatMpPlugin {
|
|||
// 输出outpath前
|
||||
})
|
||||
);
|
||||
|
||||
compiler.hooks.afterEmit.tap(
|
||||
pluginName,
|
||||
(compilation) => {
|
||||
const { options, outputOptions } = compilation;
|
||||
const { context: projectPath } = options;
|
||||
const { path: outputPath } = outputOptions;
|
||||
|
||||
this.makeI18nWxs(projectPath, outputPath);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
compilationHooks(compilation) {
|
||||
|
|
@ -278,7 +295,7 @@ class OakWeChatMpPlugin {
|
|||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {}
|
||||
} catch (error) { }
|
||||
}
|
||||
|
||||
// add script entry
|
||||
|
|
@ -675,6 +692,81 @@ class OakWeChatMpPlugin {
|
|||
...exclude,
|
||||
];
|
||||
}
|
||||
|
||||
makeI18nWxs(projectPath, outputPath) {
|
||||
const i18nWxsFilename = path.join(projectPath, 'node_modules', 'oak-frontend-base', 'lib', 'platforms', 'wechatMp', 'i18n', 'wxs.js');
|
||||
const content = readFileSync(i18nWxsFilename, { encoding: 'utf-8' });
|
||||
const ast = parseSync(content);
|
||||
const { program: { body } } = ast;
|
||||
|
||||
traverseAst(ast, {
|
||||
enter(path) {
|
||||
if (path.isRegExpLiteral()) {
|
||||
const { node } = path;
|
||||
path.replaceWith(
|
||||
t.callExpression(
|
||||
t.identifier('getRegExp'),
|
||||
[
|
||||
t.stringLiteral(node.pattern),
|
||||
t.stringLiteral(node.flags)
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
else if (path.isNewExpression()) {
|
||||
const { node } = path;
|
||||
if (t.isIdentifier(node.callee) && node.callee.name === 'RegExp') {
|
||||
const { arguments: args } = node;
|
||||
path.replaceWith(
|
||||
t.callExpression(
|
||||
t.identifier('getRegExp'),
|
||||
args
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 去掉编译中不必要的expression
|
||||
*/
|
||||
ast.program.body = ast.program.body.filter(
|
||||
ele => !t.isExpressionStatement(ele)
|
||||
);
|
||||
|
||||
/**
|
||||
* 加上module.exports = { t: t };
|
||||
*/
|
||||
ast.program.body.push(
|
||||
t.expressionStatement(
|
||||
t.assignmentExpression(
|
||||
'=',
|
||||
t.memberExpression(
|
||||
t.identifier('module'),
|
||||
t.identifier('exports')
|
||||
),
|
||||
t.objectExpression(
|
||||
[
|
||||
t.objectProperty(
|
||||
t.identifier('t'),
|
||||
t.identifier('t')
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
const { code } = transformFromAstSync(ast);
|
||||
|
||||
const wxsOutputDir = path.join(outputPath, 'wxs');
|
||||
if (!existsSync(wxsOutputDir)) {
|
||||
mkdirSync(wxsOutputDir);
|
||||
}
|
||||
const outputFilename = path.join(wxsOutputDir, 'i18n.wxs');
|
||||
writeFileSync(outputFilename, code, { flag: 'w' });
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = OakWeChatMpPlugin;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
|
||||
const { parseSync, transformFromAstSync } = require('@babel/core');
|
||||
const t = require('@babel/types');
|
||||
const assert = require('assert');
|
||||
const traverse = require('@babel/traverse').default;
|
||||
|
||||
function codeChunkIncludesT(text) {
|
||||
return /{{(\w|\W)*\W*t\((\w|\W)+\)(\w|\W)*}}/g.test(text)
|
||||
}
|
||||
|
||||
function transformCode(text, namespace, moduleName) {
|
||||
const t2 = text.replace(/{{((\w|\W)*)}}/g, '$1');
|
||||
const ast = parseSync(t2);
|
||||
traverse(ast, {
|
||||
enter(path) {
|
||||
if (path.isCallExpression()) {
|
||||
const { node } = path;
|
||||
if (t.isIdentifier(node.callee) && node.callee.name === 't') {
|
||||
const { arguments } = node;
|
||||
// 在t的后面加五个参数(oakLocales, oakLng, oakDefaultLng, oakNamespace, oakModule)
|
||||
arguments.push(
|
||||
t.identifier('oakLocales'),
|
||||
t.identifier('oakLng'),
|
||||
t.identifier('oakDefaultLng'),
|
||||
t.stringLiteral(namespace),
|
||||
t.stringLiteral(moduleName)
|
||||
);
|
||||
node.callee = t.memberExpression(
|
||||
t.identifier('i18n'),
|
||||
t.identifier('t')
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
const { code } = transformFromAstSync(ast);
|
||||
assert(code.endsWith(';'));
|
||||
return `{{${code.substring(0, code.length - 1)}}}`;
|
||||
}
|
||||
|
||||
function getRelativePath(filepath) {
|
||||
const relativePath = filepath.replace(/\\/g, '/').replace(/(\w|\W)*(\/pages\/|\/components\/)((\w|\W)*)/g, '$3');
|
||||
return relativePath;
|
||||
}
|
||||
|
||||
function getProjectPath(filepath) {
|
||||
const path = filepath.replace(/\\/g, '/').replace(/((\w|\W)*)(\/src|\/lib)(\/pages\/|\/components\/)((\w|\W)*)/g, '$1');
|
||||
return path;
|
||||
}
|
||||
|
||||
// console.log(transformCode('{{abc + t("abd")}}', 'ns', 'module'));
|
||||
console.log(getProjectPath('D:\\git\\taicang\\src\\pages\\home\\index.xml'));
|
||||
Loading…
Reference in New Issue