diff --git a/packages/jsx-compiler/package.json b/packages/jsx-compiler/package.json
index afdb500b1c..c527699e72 100644
--- a/packages/jsx-compiler/package.json
+++ b/packages/jsx-compiler/package.json
@@ -1,6 +1,6 @@
{
"name": "jsx-compiler",
- "version": "0.3.1",
+ "version": "0.3.2",
"license": "BSD-3-Clause",
"description": "Parser for Rax JSX Statements.",
"main": "src/index.js",
@@ -25,6 +25,7 @@
"fs-extra": "^7.0.1",
"kebab-case": "^1.0.0",
"md5": "^2.2.1",
+ "resolve": "^1.12.0",
"stylesheet-loader": "^0.6.6-0"
},
"devDependencies": {
diff --git a/packages/jsx-compiler/src/codegen/genCode.js b/packages/jsx-compiler/src/codegen/genCode.js
index 75a325ad93..f2841e39c3 100644
--- a/packages/jsx-compiler/src/codegen/genCode.js
+++ b/packages/jsx-compiler/src/codegen/genCode.js
@@ -1,6 +1,9 @@
const generate = require('@babel/generator').default;
-const generateOptions = {};
+const generateOptions = {
+ sourceFileName: '',
+ sourceMaps: true
+};
/**
* Generate code and map from babel ast.
diff --git a/packages/jsx-compiler/src/codegen/index.js b/packages/jsx-compiler/src/codegen/index.js
index 6d1b8fdb5e..4ad45717e3 100644
--- a/packages/jsx-compiler/src/codegen/index.js
+++ b/packages/jsx-compiler/src/codegen/index.js
@@ -3,7 +3,7 @@ const genCode = require('./genCode');
const { baseOptions } = require('../options');
function generate(parsed, options = baseOptions) {
- const { code, map } = genCode(parsed.ast);
+ const { code, map } = genCode(parsed.ast, options);
const ret = {
code, map,
// config, template, style and others should be generated in plugin modules.
diff --git a/packages/jsx-compiler/src/modules/__tests__/attribute.js b/packages/jsx-compiler/src/modules/__tests__/attribute.js
new file mode 100644
index 0000000000..6d7a22a3d1
--- /dev/null
+++ b/packages/jsx-compiler/src/modules/__tests__/attribute.js
@@ -0,0 +1,27 @@
+const t = require('@babel/types');
+const { _transformAttribute } = require('../attribute');
+const { parseExpression } = require('../../parser');
+const adapter = require('../../adapter').ali;
+const genCode = require('../../codegen/genCode');
+
+describe('Transform JSX Attribute', () => {
+ it('should transform attribute name is key', () => {
+ const code = 'test';
+ const ast = parseExpression(code);
+ _transformAttribute(ast, code, adapter);
+ expect(genCode(ast).code).toEqual('test');
+ });
+ it('should transform attribute name is className', () => {
+ const code = 'test';
+ const ast = parseExpression(code);
+ _transformAttribute(ast, code, adapter);
+ expect(genCode(ast).code).toEqual('test');
+ });
+ it("should collect attribute name is ref and parse it's value as a string", () => {
+ const code = 'test';
+ const ast = parseExpression(code);
+ const refs = _transformAttribute(ast, code, adapter);
+ expect(genCode(ast).code).toEqual('test');
+ expect(refs).toEqual([t.stringLiteral('scrollViewRef')]);
+ });
+});
diff --git a/packages/jsx-compiler/src/modules/__tests__/condition.js b/packages/jsx-compiler/src/modules/__tests__/condition.js
index e3f254003a..01df831fe9 100644
--- a/packages/jsx-compiler/src/modules/__tests__/condition.js
+++ b/packages/jsx-compiler/src/modules/__tests__/condition.js
@@ -39,7 +39,7 @@ describe('Transform condition', () => {
const dynamicValue = _transformTemplate(ast, adapter, {});
expect(genCode(ast).code).toEqual('');
- expect(genDynamicValue(dynamicValue)).toEqual('{ foo: foo, bar: bar }');
+ expect(genDynamicValue(dynamicValue)).toEqual('{ bar: bar, foo: foo }');
});
it("transform condition's alternate is conditional expression", () => {
@@ -49,7 +49,7 @@ describe('Transform condition', () => {
const dynamicValue = _transformTemplate(ast, adapter, {});
expect(genCode(ast).code).toEqual('xxx');
- expect(genDynamicValue(dynamicValue)).toEqual('{ empty: empty, loading: loading }');
+ expect(genDynamicValue(dynamicValue)).toEqual('{ loading: loading, empty: empty }');
});
it('skip list dynamic value', () => {
@@ -88,6 +88,30 @@ describe('Transform condition', () => {
`);
expect(genDynamicValue(dynamicValue)).toEqual('{ tabList: tabList }');
});
+
+ it('transform simple logical expression', () => {
+ const ast = parseExpression(`
+
+ { a && 1}
+
+ `);
+ _transformTemplate(ast, adapter, {});
+ expect(genCode(ast).code).toEqual(`
+ 1{a}
+ `);
+ });
+
+ it('transform nested logical expression', () => {
+ const ast = parseExpression(`
+
+ { a || b && 1}
+
+ `);
+ _transformTemplate(ast, adapter, {});
+ expect(genCode(ast).code).toEqual(`
+ 1{b}{a}
+ `);
+ });
});
describe('Transiform condition render function', () => {
diff --git a/packages/jsx-compiler/src/modules/__tests__/element.js b/packages/jsx-compiler/src/modules/__tests__/element.js
index d281529499..2634ebd1d7 100644
--- a/packages/jsx-compiler/src/modules/__tests__/element.js
+++ b/packages/jsx-compiler/src/modules/__tests__/element.js
@@ -109,15 +109,6 @@ describe('Transform JSXElement', () => {
expect(genInlineCode(ast).code).toEqual('{{ _d0 ? _d0.b[_d1.d] : 1 }}');
expect(genDynamicAttrs(dynamicValues)).toEqual('{ _d0: a, _d1: c }');
});
-
- it('should adapt attribute key', () => {
- const sourceCode = '{ bar }';
- const ast = parseExpression(sourceCode);
- const { dynamicValues } = _transform(ast, null, adapter, sourceCode);
- const code = genInlineCode(ast).code;
- expect(code).toEqual('{{ _d0 }}');
- expect(genDynamicAttrs(dynamicValues)).toEqual('{ _d0: bar }');
- });
});
describe('event handlers', () => {
diff --git a/packages/jsx-compiler/src/modules/attribute.js b/packages/jsx-compiler/src/modules/attribute.js
new file mode 100644
index 0000000000..9020d65b60
--- /dev/null
+++ b/packages/jsx-compiler/src/modules/attribute.js
@@ -0,0 +1,44 @@
+const t = require('@babel/types');
+const traverse = require('../utils/traverseNodePath');
+const genExpression = require('../codegen/genExpression');
+const CodeError = require('../utils/CodeError');
+
+function transformAttribute(ast, code, adapter) {
+ const refs = [];
+ traverse(ast, {
+ JSXAttribute(path) {
+ const { node } = path;
+ const attrName = node.name.name;
+ switch (attrName) {
+ case 'key':
+ node.name.name = adapter.key;
+ break;
+ case 'className':
+ node.name.name = adapter.className;
+ break;
+ case 'ref':
+ if (t.isJSXExpressionContainer(node.value)) {
+ node.value = t.stringLiteral(genExpression(node.value.expression));
+ }
+ if (t.isStringLiteral(node.value)) {
+ refs.push(node.value);
+ } else {
+ throw new CodeError(code, node, path.loc, "Ref's type must be string or jsxExpressionContainer");
+ }
+ break;
+ default:
+ path.stop();
+ }
+ }
+ });
+ return refs;
+}
+
+module.exports = {
+ parse(parsed, code, options) {
+ parsed.refs = transformAttribute(parsed.templateAST, code, options.adapter);
+ },
+
+ // For test cases.
+ _transformAttribute: transformAttribute,
+};
diff --git a/packages/jsx-compiler/src/modules/code.js b/packages/jsx-compiler/src/modules/code.js
index bccc231722..15e259dd4d 100644
--- a/packages/jsx-compiler/src/modules/code.js
+++ b/packages/jsx-compiler/src/modules/code.js
@@ -20,17 +20,16 @@ const SAFE_CREATE_STYLE = '__create_style__';
const USE_EFFECT = 'useEffect';
const USE_STATE = 'useState';
const USE_CONTEXT = 'useContext';
-const USE_HISTORY = 'useHistory';
-const USE_LOCATION = 'useLocation';
+const USE_REF = 'useRef';
const EXPORTED_DEF = '__def__';
-const RUNTIME = '/npm/jsx2mp-runtime';
+const RUNTIME = 'jsx2mp-runtime';
+const getRuntimeByPlatform = (platform) => `${RUNTIME}/dist/jsx2mp-runtime.${platform}.esm`;
const isAppRuntime = (mod) => mod === 'rax-app';
const isFileModule = (mod) => /\.(png|jpe?g|gif|bmp|webp)$/.test(mod);
-const isHooksAPI = (node) => [USE_EFFECT, USE_STATE, USE_CONTEXT,
- USE_HISTORY, USE_LOCATION].includes(node.name);
+const isCoreHooksAPI = (node) => [USE_EFFECT, USE_STATE, USE_CONTEXT, USE_REF].includes(node.name);
function getConstructor(type) {
switch (type) {
@@ -50,13 +49,14 @@ function getConstructor(type) {
module.exports = {
parse(parsed, code, options) {
const { defaultExportedPath, eventHandlers = [] } = parsed;
- if (options.type !== 'app' && (!defaultExportedPath || !defaultExportedPath.node)) {
+ const { platform, type, cwd, outputPath, sourcePath, resourcePath, disableCopyNpm } = options;
+ if (type !== 'app' && (!defaultExportedPath || !defaultExportedPath.node)) {
// Can not found default export, otherwise app.js is excluded.
return;
}
let userDefineType;
- if (options.type === 'app') {
+ if (type === 'app') {
userDefineType = 'function';
} else if (isFunctionComponent(defaultExportedPath)) { // replace with class def.
userDefineType = 'function';
@@ -91,20 +91,22 @@ module.exports = {
}
const hooks = collectHooks(parsed.renderFunctionPath);
-
- const targetFileDir = dirname(join(options.outputPath, relative(options.sourcePath, options.resourcePath)));
+ const targetFileDir = dirname(join(outputPath, relative(sourcePath, resourcePath)));
+ const runtimePath = getRuntimePath(outputPath, targetFileDir, platform, disableCopyNpm);
removeRaxImports(parsed.ast);
- renameCoreModule(parsed.ast, options.outputPath, targetFileDir);
+ renameCoreModule(parsed.ast, runtimePath);
renameFileModule(parsed.ast);
- renameAppConfig(parsed.ast, options.sourcePath, options.resourcePath);
+ renameAppConfig(parsed.ast, sourcePath, resourcePath);
- const currentNodeModulePath = join(options.sourcePath, 'npm');
- const npmRelativePath = relative(dirname(options.resourcePath), currentNodeModulePath);
- renameNpmModules(parsed.ast, npmRelativePath, options.resourcePath, options.cwd);
+ if (!disableCopyNpm) {
+ const currentNodeModulePath = join(sourcePath, 'npm');
+ const npmRelativePath = relative(dirname(resourcePath), currentNodeModulePath);
+ renameNpmModules(parsed.ast, npmRelativePath, resourcePath, cwd);
+ }
- if (options.type !== 'app') {
- addDefine(parsed.ast, options.type, options.outputPath, targetFileDir, userDefineType, eventHandlers, parsed.useCreateStyle, hooks);
+ if (type !== 'app') {
+ addDefine(parsed.ast, type, userDefineType, eventHandlers, parsed.useCreateStyle, hooks, runtimePath);
}
removeDefaultImports(parsed.ast);
@@ -112,7 +114,7 @@ module.exports = {
/**
* updateChildProps: collect props dependencies.
*/
- if (options.type !== 'app' && parsed.renderFunctionPath) {
+ if (type !== 'app' && parsed.renderFunctionPath) {
const fnBody = parsed.renderFunctionPath.node.body.body;
let firstReturnStatementIdx = -1;
for (let i = 0, l = fnBody.length; i < l; i++) {
@@ -159,6 +161,7 @@ module.exports = {
addUpdateData(parsed.dynamicValue, parsed.renderItemFunctions, parsed.renderFunctionPath);
addUpdateEvent(parsed.dynamicEvents, parsed.eventHandler, parsed.renderFunctionPath);
addProviderIniter(parsed.contextList, parsed.renderFunctionPath);
+ addRegisterRefs(parsed.refs, parsed.renderFunctionPath);
}
},
};
@@ -176,14 +179,21 @@ function genTagIdExp(expressions) {
return parseExpression(ret);
}
-function renameCoreModule(ast, outputPath, targetFileDir) {
+function getRuntimePath(outputPath, targetFileDir, platform, disableCopyNpm) {
+ let runtimePath = getRuntimeByPlatform(platform.type);
+ if (!disableCopyNpm) {
+ runtimePath = relative(targetFileDir, join(outputPath, 'npm', RUNTIME));
+ runtimePath = runtimePath[0] !== '.' ? './' + runtimePath : runtimePath;
+ }
+ return runtimePath;
+}
+
+function renameCoreModule(ast, runtimePath) {
traverse(ast, {
ImportDeclaration(path) {
const source = path.get('source');
if (source.isStringLiteral() && isAppRuntime(source.node.value)) {
- let runtimeRelativePath = relative(targetFileDir, join(outputPath, RUNTIME));
- runtimeRelativePath = runtimeRelativePath[0] !== '.' ? './' + runtimeRelativePath : runtimeRelativePath;
- source.replaceWith(t.stringLiteral(runtimeRelativePath));
+ source.replaceWith(t.stringLiteral(runtimePath));
}
}
});
@@ -282,7 +292,7 @@ function renameNpmModules(ast, npmRelativePath, filename, cwd) {
});
}
-function addDefine(ast, type, outputPath, targetFileDir, userDefineType, eventHandlers, useCreateStyle, hooks) {
+function addDefine(ast, type, userDefineType, eventHandlers, useCreateStyle, hooks, runtimePath) {
let safeCreateInstanceId;
let importedIdentifier;
switch (type) {
@@ -323,13 +333,10 @@ function addDefine(ast, type, outputPath, targetFileDir, userDefineType, eventHa
));
}
- let runtimeRelativePath = relative(targetFileDir, join(outputPath, RUNTIME));
- runtimeRelativePath = runtimeRelativePath[0] !== '.' ? './' + runtimeRelativePath : runtimeRelativePath;
-
path.node.body.unshift(
t.importDeclaration(
specifiers,
- t.stringLiteral(runtimeRelativePath)
+ t.stringLiteral(runtimePath)
)
);
@@ -405,7 +412,7 @@ function collectHooks(root) {
traverse(root, {
CallExpression(path) {
const { node } = path;
- if (t.isIdentifier(node.callee) && isHooksAPI(node.callee)) {
+ if (t.isIdentifier(node.callee) && isCoreHooksAPI(node.callee)) {
ret[node.callee.name] = true;
}
}
@@ -468,6 +475,51 @@ function addProviderIniter(contextList, renderFunctionPath) {
}
}
+/**
+ * Insert register ref method
+ * @param {Array} refs
+ * @param {Object} renderFunctionPath
+ * */
+function addRegisterRefs(refs, renderFunctionPath) {
+ const registerRefsMethods = t.memberExpression(
+ t.thisExpression(),
+ t.identifier('_registerRefs')
+ );
+ const fnBody = renderFunctionPath.node.body.body;
+ /**
+ * this._registerRefs([
+ * {
+ * name: 'scrollViewRef',
+ * method: scrollViewRef
+ * }
+ * ])
+ * */
+ const scopedRefs = [];
+ const stringRefs = [];
+ refs.map(ref => {
+ if (renderFunctionPath.scope.hasBinding(ref.value)) {
+ scopedRefs.push(ref);
+ } else {
+ stringRefs.push(ref);
+ }
+ });
+ if (scopedRefs.length > 0) {
+ fnBody.push(t.expressionStatement(t.callExpression(registerRefsMethods, [
+ t.arrayExpression(scopedRefs.map(ref => {
+ return t.objectExpression([t.objectProperty(t.stringLiteral('name'), ref),
+ t.objectProperty(t.stringLiteral('method'), t.identifier(ref.value))]);
+ }))
+ ])));
+ }
+ if (stringRefs.length > 0) {
+ fnBody.unshift(t.expressionStatement(t.callExpression(registerRefsMethods, [
+ t.arrayExpression(stringRefs.map(ref => {
+ return t.objectExpression([t.objectProperty(t.stringLiteral('name'), ref)]);
+ }))
+ ])));
+ }
+}
+
/**
* For that alipay build folder can not contain `@`, escape to `_`.
*/
diff --git a/packages/jsx-compiler/src/modules/components.js b/packages/jsx-compiler/src/modules/components.js
index b0c06b52d4..f722977ed6 100644
--- a/packages/jsx-compiler/src/modules/components.js
+++ b/packages/jsx-compiler/src/modules/components.js
@@ -1,5 +1,6 @@
-const { join, relative, dirname } = require('path');
+const { join, relative, dirname, resolve } = require('path');
const { readJSONSync } = require('fs-extra');
+const resolveModule = require('resolve');
const t = require('@babel/types');
const { _transform: transformTemplate } = require('./element');
const genExpression = require('../codegen/genExpression');
@@ -278,7 +279,7 @@ function getComponentConfig(pkgName, resourcePath) {
// for tnpm, the package name will be like _rax-image@1.1.2@rax-image
function getRealNpmPkgName(filePath) {
const result = PKG_NAME_REG.exec(filePath);
- return result && result[1].replace(/@/g, '_');
+ return result && result[1];
}
function getComponentPath(alias, options) {
@@ -293,15 +294,24 @@ function getComponentPath(alias, options) {
moduleResolve(options.resourcePath, alias.from, '.js');
return filename;
} else {
- const realNpmFile = require.resolve(alias.from, { paths: [options.resourcePath] });
+ const { disableCopyNpm } = options;
+ const realNpmFile = resolveModule.sync(alias.from, { basedir: dirname(options.resourcePath), preserveSymlinks: false });
const pkgName = getRealNpmPkgName(realNpmFile);
// npm module
const pkg = getComponentConfig(alias.from, options.resourcePath);
if (pkg.miniappConfig && pkg.miniappConfig.main) {
+ if (disableCopyNpm) {
+ return join(pkg.name, pkg.miniappConfig.main);
+ }
+
const targetFileDir = dirname(join(options.outputPath, relative(options.sourcePath, options.resourcePath)));
let npmRelativePath = relative(targetFileDir, join(options.outputPath, '/npm'));
npmRelativePath = npmRelativePath[0] !== '.' ? './' + npmRelativePath : npmRelativePath;
- return './' + join(npmRelativePath, pkgName, pkg.miniappConfig.main);
+
+ const miniappConfigRelativePath = relative(pkg.main, pkg.miniappConfig.main);
+ const realMiniappAbsPath = resolve(realNpmFile, miniappConfigRelativePath);
+ const realMiniappRelativePath = realMiniappAbsPath.slice(realMiniappAbsPath.indexOf(pkgName) + pkgName.length);
+ return './' + join(npmRelativePath, pkgName.replace(/@/g, '_'), realMiniappRelativePath);
} else {
console.warn(
'Can not found compatible rax miniapp component "' + pkg.name + '".',
diff --git a/packages/jsx-compiler/src/modules/condition.js b/packages/jsx-compiler/src/modules/condition.js
index 15000cdda2..46f3d9554b 100644
--- a/packages/jsx-compiler/src/modules/condition.js
+++ b/packages/jsx-compiler/src/modules/condition.js
@@ -3,6 +3,8 @@ const traverse = require('../utils/traverseNodePath');
const createJSX = require('../utils/createJSX');
const createBinding = require('../utils/createBinding');
const genExpression = require('../codegen/genExpression');
+const CodeError = require('../utils/CodeError');
+const chalk = require('chalk');
const TEMPLATE_AST = 'templateAST';
const RENDER_FN_PATH = 'renderFunctionPath';
@@ -57,7 +59,8 @@ function transformRenderFunction(ast, adapter) {
consequent.body.map(({ expression }) => {
if (
t.isAssignmentExpression(expression) &&
- expression.operator === '='
+ expression.operator === '=' &&
+ t.isIdentifier(expression.left)
) {
let shouldRemove = false;
const varName = expression.left.name;
@@ -66,10 +69,6 @@ function transformRenderFunction(ast, adapter) {
'block',
);
}
- /**
- * Todo:
- * 1. value tranmits
- */
let testAttrName = adapter.if;
const parentPathAlternate = nodePath.parent.alternate;
/**
@@ -110,7 +109,8 @@ function transformRenderFunction(ast, adapter) {
alternate.body.map(({ expression }) => {
if (
t.isAssignmentExpression(expression) &&
- expression.operator === '='
+ expression.operator === '=' &&
+ t.isIdentifier(expression.left)
) {
const varName = expression.left.name;
const rightNode = expression.right;
@@ -138,7 +138,7 @@ function transformRenderFunction(ast, adapter) {
return templateVariables;
}
-function transformTemplate(ast, adapter, templateVariables) {
+function transformTemplate(ast, adapter, templateVariables, code) {
const dynamicValue = {};
traverse(ast, {
@@ -151,7 +151,7 @@ function transformTemplate(ast, adapter, templateVariables) {
switch (node.expression.type) {
case 'ConditionalExpression': {
- const { replacement } = transformConditionalExpression(path, path.node.expression, adapter, dynamicValue);
+ const { replacement } = transformConditionalExpression(path, node.expression, { adapter, dynamicValue, code });
path.replaceWithMultiple(replacement);
break;
}
@@ -171,24 +171,65 @@ function transformTemplate(ast, adapter, templateVariables) {
}
}
},
+ LogicalExpression(path) {
+ if (path.parentPath.isJSXExpressionContainer()) {
+ const { right, left, operator } = path.node;
+ let replacement = [];
+ if (isJSX(left)) {
+ if (operator === '&&') {
+ replacement.push(right);
+ } else if (operator === '||') {
+ replacement.push(left);
+ } else {
+ throw new CodeError(code, path.node, path.node.loc, 'Logical operator only support && or ||');
+ }
+ } else {
+ let test;
+ if (operator === '||') {
+ test = t.unaryExpression('!', left);
+ } else if (operator === '&&') {
+ test = left;
+ } else {
+ throw new CodeError(code, path.node, path.node.loc, 'Logical operator only support && or ||');
+ }
+ const children = [];
+ if (/Expression$/.test(right.type)) {
+ children.push(t.jsxExpressionContainer(right));
+ } else {
+ children.push(right);
+ }
+ replacement.push(createJSX('block', {
+ [adapter.if]: generateConditionValue(test, {adapter, dynamicValue})
+ }, children));
+ replacement.push(createJSX('block', {
+ [adapter.else]: null,
+ }, [t.jsxExpressionContainer(left)]));
+ if (/Expression$/.test(left.type)) {
+ console.log(chalk.yellow("When logicalExpression's left node is an expression, please write JSX directly instead of using a variable which is assigned a JSX element."
+ ));
+ }
+ }
+ path.parentPath.replaceWithMultiple(replacement);
+ } else {
+ path.skip();
+ }
+ }
});
return dynamicValue;
}
-function transformConditionalExpression(path, expression, adapter, dynamicValue) {
+/**
+ * @param {Object} path
+ * jsxExpressionContainer
+ * @param {Object} expression
+ * Condition Expression
+ * @param {{ adapter: Object, dynamicValue: Object, code: String }} options
+ * @returns {{ replacement: Object, dynamicValue: Object }}
+ * */
+function transformConditionalExpression(path, expression, options) {
let { test, consequent, alternate } = expression;
- let conditionValue;
- if (/Expression$/.test(test.type)) {
- conditionValue = t.jsxExpressionContainer(test);
- } else if (t.isStringLiteral(test)) {
- conditionValue = test;
- } else {
- // Other literal types or identifier.
- conditionValue = t.stringLiteral(createBinding(genExpression(test)));
- if (t.isIdentifier(test)) dynamicValue[test.name] = test;
- }
const replacement = [];
let consequentReplacement = [];
let alternateReplacement = [];
@@ -198,8 +239,7 @@ function transformConditionalExpression(path, expression, adapter, dynamicValue)
consequentReplacement = transformConditionalExpression(
path,
consequent,
- adapter,
- dynamicValue
+ options
).replacement;
} else if (/Literal$/.test(consequent.type)) {
// Transform from literal type to JSXText Node
@@ -217,8 +257,7 @@ function transformConditionalExpression(path, expression, adapter, dynamicValue)
alternateReplacement = transformConditionalExpression(
path,
alternate,
- adapter,
- dynamicValue
+ options
).replacement;
} else if (t.isNullLiteral(alternate)) {
// Ignore null
@@ -236,7 +275,7 @@ function transformConditionalExpression(path, expression, adapter, dynamicValue)
createJSX(
'block',
{
- [adapter.if]: conditionValue,
+ [options.adapter.if]: generateConditionValue(test, options),
},
consequentReplacement,
),
@@ -247,13 +286,13 @@ function transformConditionalExpression(path, expression, adapter, dynamicValue)
createJSX(
'block',
{
- [adapter.else]: null,
+ [options.adapter.else]: null,
},
alternateReplacement,
),
);
}
- return { replacement, dynamicValue };
+ return { replacement, dynamicValue: options.dynamicValue };
}
module.exports = {
@@ -265,7 +304,7 @@ module.exports = {
if (t.isIdentifier(parsed[TEMPLATE_AST]) && parsed[TEMPLATE_AST].name in templateVariables) {
parsed[TEMPLATE_AST] = templateVariables[parsed[TEMPLATE_AST].name].value;
} else {
- const dynamicValue = transformTemplate(parsed[TEMPLATE_AST], options.adapter, templateVariables);
+ const dynamicValue = transformTemplate(parsed[TEMPLATE_AST], options.adapter, templateVariables, code);
Object.assign(parsed.dynamicValue = parsed.dynamicValue || {}, dynamicValue);
}
},
@@ -274,3 +313,21 @@ module.exports = {
_transformRenderFunction: transformRenderFunction,
_transformTemplate: transformTemplate,
};
+
+function generateConditionValue(test, options) {
+ let conditionValue;
+ if (/Expression$/.test(test.type)) {
+ conditionValue = t.jsxExpressionContainer(test);
+ } else if (t.isStringLiteral(test)) {
+ conditionValue = test;
+ } else {
+ // Other literal types or identifier.
+ conditionValue = t.stringLiteral(createBinding(genExpression(test)));
+ if (t.isIdentifier(test)) options.dynamicValue[test.name] = test;
+ }
+ return conditionValue;
+}
+
+function isJSX(node) {
+ return ['JSXElement', 'JSXText', 'JSXFragment'].indexOf(node.type) > -1;
+}
diff --git a/packages/jsx-compiler/src/modules/element.js b/packages/jsx-compiler/src/modules/element.js
index 62cd62e015..03fb6e7980 100644
--- a/packages/jsx-compiler/src/modules/element.js
+++ b/packages/jsx-compiler/src/modules/element.js
@@ -328,24 +328,6 @@ function transformTemplate(ast, scope = null, adapter, sourceCode, componentDepe
}
traverse(ast, {
- JSXAttribute(path) {
- const { node } = path;
- const attrName = node.name.name;
- // adapt the key attribute
- if (attrName === 'key') {
- node.name.name = adapter.key;
- }
- // Remove ref.
- if (attrName === 'ref') {
- path.remove();
- }
- if (attrName === 'className') {
- node.name.name = adapter.className;
- }
- // if (attrName === 'style') {
- // node.name.name = adapter.style;
- // }
- },
JSXExpressionContainer: handleJSXExpressionContainer,
JSXOpeningElement: {
exit(path) {
diff --git a/packages/jsx-compiler/src/modules/index.js b/packages/jsx-compiler/src/modules/index.js
index 732b9caff1..31063b65f0 100644
--- a/packages/jsx-compiler/src/modules/index.js
+++ b/packages/jsx-compiler/src/modules/index.js
@@ -11,6 +11,8 @@ module.exports = [
require('./code'),
// Handle template attrs
require('./element'),
+ // Handle jsx attribute
+ require('./attribute'),
// Handle template style attr
require('./style'),
// Handle Rax base components.
diff --git a/packages/jsx-compiler/src/modules/jsx-plus.js b/packages/jsx-compiler/src/modules/jsx-plus.js
index 4d979e85b3..61edd0a6cb 100644
--- a/packages/jsx-compiler/src/modules/jsx-plus.js
+++ b/packages/jsx-compiler/src/modules/jsx-plus.js
@@ -226,6 +226,7 @@ module.exports = {
if (parsed.renderFunctionPath) {
transformDirectiveCondition(parsed.templateAST, options.adapter);
transformDirectiveList(parsed.templateAST, code, options.adapter);
+ transformComponentFragment(parsed.templateAST);
}
},
_transformList: transformDirectiveList,
diff --git a/packages/jsx2mp-cli/__tests__/component.js b/packages/jsx2mp-cli/__tests__/component.js
index 4ef7597282..233ac2a1da 100644
--- a/packages/jsx2mp-cli/__tests__/component.js
+++ b/packages/jsx2mp-cli/__tests__/component.js
@@ -33,24 +33,12 @@ describe('Component compiled result', () => {
expect(axmlContent).toEqual(
`
Hello World!
-
- `);
+ `);
});
it('should return correct js', () => {
expect(jsContent).toEqual(
- `import { createComponent as __create_component__ } from "./npm/jsx2mp-runtime";
-const img = "./assets/rax.png";
-
-const __def__ = function Index() {
- this._updateData({
- "_d0": img
- });
-
- this._updateMethods({});
-};
-
-Component(__create_component__(__def__));`
+ '\"use strict\";var _jsx2mpRuntime=require(\"./npm/jsx2mp-runtime\"),img=\"./assets/rax.png\",__def__=function(){this._updateData({_d0:img}),this._updateMethods({})};Component((0,_jsx2mpRuntime.createComponent)(__def__));'
);
});
diff --git a/packages/jsx2mp-cli/bin/jsx2mp-build.js b/packages/jsx2mp-cli/bin/jsx2mp-build.js
index 9fa5646955..7eeb3ca8e0 100644
--- a/packages/jsx2mp-cli/bin/jsx2mp-build.js
+++ b/packages/jsx2mp-cli/bin/jsx2mp-build.js
@@ -9,7 +9,8 @@ program
.option('-e, --entry ', 'set entry of component', DEFAULT_ENTRY)
.option('-d, --dist ', 'set export path', DEFAULT_DIST)
.option('-s, --skip-clear-stdout', 'skip clear stdout of screen', false)
- .option('-c --constant-dir ', 'set constant directory to copy', DEFAULT_CONSTANT_DIR)
+ .option('-c, --constant-dir ', 'set constant directory to copy', DEFAULT_CONSTANT_DIR)
+ .option('-n, --disable-copy-npm', 'disable copy node_modules action', false)
.action((cmd) => {
const workDirectory = resolve(process.env.CWD || process.cwd());
const distDirectory = resolve(workDirectory, cmd.dist);
@@ -23,7 +24,8 @@ program
dist: cmd.dist,
platform: cmd.platform,
skipClearStdout: cmd.skipClearStdout,
- constantDir: cmd.constantDir === '' ? [] : cmd.constantDir.split(',').map(c => c.trim())
+ constantDir: cmd.constantDir === '' ? [] : cmd.constantDir.split(',').map(c => c.trim()),
+ disableCopyNpm: cmd.disableCopyNpm
};
require('..').build(options);
diff --git a/packages/jsx2mp-cli/bin/jsx2mp-start.js b/packages/jsx2mp-cli/bin/jsx2mp-start.js
index a6b3a24ca2..3cd620b1ca 100644
--- a/packages/jsx2mp-cli/bin/jsx2mp-start.js
+++ b/packages/jsx2mp-cli/bin/jsx2mp-start.js
@@ -9,7 +9,8 @@ program
.option('-e, --entry ', 'set entry of component', DEFAULT_ENTRY)
.option('-d, --dist ', 'set export path', DEFAULT_DIST)
.option('-s, --skip-clear-stdout', 'skip clear stdout of screen', false)
- .option('-c --constant-dir ', 'set constant directory to copy', DEFAULT_CONSTANT_DIR)
+ .option('-c, --constant-dir ', 'set constant directory to copy', DEFAULT_CONSTANT_DIR)
+ .option('-n, --disable-copy-npm', 'disable copy node_modules action', false)
.action((cmd) => {
const workDirectory = resolve(process.env.CWD || process.cwd());
const distDirectory = resolve(workDirectory, cmd.dist);
@@ -23,9 +24,9 @@ program
dist: cmd.dist,
platform: cmd.platform,
skipClearStdout: cmd.skipClearStdout,
- constantDir: cmd.constantDir === '' ? [] : cmd.constantDir.split(',').map(c => c.trim())
+ constantDir: cmd.constantDir === '' ? [] : cmd.constantDir.split(',').map(c => c.trim()),
+ disableCopyNpm: cmd.disableCopyNpm
};
-
require('..').watch(options);
});
diff --git a/packages/jsx2mp-cli/getWebpackConfig.js b/packages/jsx2mp-cli/getWebpackConfig.js
index f08c4c98a7..59cac06270 100644
--- a/packages/jsx2mp-cli/getWebpackConfig.js
+++ b/packages/jsx2mp-cli/getWebpackConfig.js
@@ -33,12 +33,14 @@ function getBabelConfig() {
function getEntry(type, cwd, entryFilePath, options) {
const entryPath = dirname(entryFilePath);
const entry = {};
- const { platform = 'ali', constantDir } = options;
+ const { platform = 'ali', constantDir, mode, disableCopyNpm } = options;
const loaderParams = JSON.stringify({
platform: platformConfig[platform],
entryPath: entryFilePath,
- constantDir
+ constantDir,
+ mode,
+ disableCopyNpm
});
if (type === 'project') {
@@ -50,9 +52,11 @@ function getEntry(type, cwd, entryFilePath, options) {
console.error('Can not found app.json in current work directory, please check.');
process.exit(1);
}
- entry.app = AppLoader + '?' + JSON.stringify({ entryPath, platform: platformConfig[platform] }) + '!./' + join(entryPath, 'app.js');
+ entry.app = AppLoader + '?' + JSON.stringify({ entryPath, platform: platformConfig[platform], mode, disableCopyNpm }) + '!./' + join(entryPath, 'app.js');
if (Array.isArray(appConfig.routes)) {
- appConfig.routes.forEach(({ source, component }) => {
+ appConfig.routes.filter(({ targets }) => {
+ return !Array.isArray(targets) || targets.indexOf('miniapp') > -1;
+ }).forEach(({ source, component }) => {
component = source || component;
entry['page@' + component] = PageLoader + '?' + loaderParams + '!' + getDepPath(component, entryPath);
});
@@ -85,12 +89,12 @@ function getDepPath(path, rootContext) {
const cwd = process.cwd();
module.exports = (options = {}) => {
- let { entryPath, type, workDirectory, distDirectory, platform = 'ali', mode } = options;
+ let { entryPath, type, workDirectory, distDirectory, platform = 'ali', mode, disableCopyNpm } = options;
if (entryPath[0] !== '.') entryPath = './' + entryPath;
entryPath = moduleResolve(workDirectory, entryPath, '.js') || moduleResolve(workDirectory, entryPath, '.jsx') || entryPath;
const relativeEntryFilePath = './' + relative(workDirectory, entryPath); // src/app.js or src/mobile/index.js
- return {
+ const config = {
mode: 'production', // Will be fast
entry: getEntry(type, cwd, relativeEntryFilePath, options),
output: {
@@ -108,7 +112,8 @@ module.exports = (options = {}) => {
options: {
mode: options.mode,
entryPath: relativeEntryFilePath,
- platform: platformConfig[platform]
+ platform: platformConfig[platform],
+ disableCopyNpm: options.disableCopyNpm
},
},
{
@@ -146,7 +151,6 @@ module.exports = (options = {}) => {
NODE_ENV: mode === 'build' ? '"production"' : '"development"',
}
}),
- new RuntimeWebpackPlugin({ platform }),
new webpack.ProgressPlugin( (percentage, message) => {
if (percentage === 0) {
buildStartTime = Date.now();
@@ -158,4 +162,9 @@ module.exports = (options = {}) => {
})
],
};
+
+ if (!disableCopyNpm) {
+ config.plugins.push(new RuntimeWebpackPlugin({ platform, mode }));
+ }
+ return config;
};
diff --git a/packages/jsx2mp-cli/index.js b/packages/jsx2mp-cli/index.js
index 629243423f..e5e4ad0f3a 100644
--- a/packages/jsx2mp-cli/index.js
+++ b/packages/jsx2mp-cli/index.js
@@ -4,6 +4,7 @@ const mergeWebpack = require('webpack-merge');
const webpack = require('webpack');
const consoleClear = require('console-clear');
const chalk = require('chalk');
+const del = require('del');
const getWebpackConfig = require('./getWebpackConfig');
const spinner = require('./utils/spinner');
const { getCurrentDirectoryPath } = require('./utils/file');
@@ -26,9 +27,15 @@ function build(options = {}) {
workDirectory = cwd,
distDirectory = join(cwd, DEFAULT_DIST),
skipClearStdout = false,
- constantDir = DEFAULT_CONSTANT_DIR_ARR
+ constantDir = DEFAULT_CONSTANT_DIR_ARR,
+ disableCopyNpm = false
} = options;
+ // Clean the dist dir before generating
+ if (existsSync(distDirectory)) {
+ del.sync(distDirectory + '/**');
+ }
+
if (type === DEFAULT_TYPE) {
copyConstantDir(constantDir, distDirectory);
} else {
@@ -45,7 +52,8 @@ function build(options = {}) {
type,
workDirectory,
distDirectory,
- constantDir
+ constantDir,
+ disableCopyNpm
});
if (options.webpackConfig) {
@@ -74,7 +82,8 @@ function watch(options = {}) {
workDirectory = cwd,
distDirectory = join(cwd, DEFAULT_DIST),
skipClearStdout = false,
- constantDir = DEFAULT_CONSTANT_DIR_ARR
+ constantDir = DEFAULT_CONSTANT_DIR_ARR,
+ disableCopyNpm = false
} = options;
if (type === DEFAULT_TYPE) {
@@ -93,7 +102,8 @@ function watch(options = {}) {
workDirectory,
platform,
distDirectory,
- constantDir
+ constantDir,
+ disableCopyNpm
});
if (options.webpackConfig) {
diff --git a/packages/jsx2mp-cli/package.json b/packages/jsx2mp-cli/package.json
index bf72f0416c..8556199719 100644
--- a/packages/jsx2mp-cli/package.json
+++ b/packages/jsx2mp-cli/package.json
@@ -1,6 +1,6 @@
{
"name": "jsx2mp-cli",
- "version": "0.2.2",
+ "version": "0.2.3",
"license": "BSD-3-Clause",
"description": "A cli tool to transform Rax JSX based project to MiniApp.",
"bin": {
@@ -24,6 +24,7 @@
"chalk": "^2.4.2",
"commander": "^2.20.0",
"console-clear": "^1.1.1",
+ "del": "^5.1.0",
"fs-extra": "^7.0.1",
"inquirer": "^6.2.2",
"jsx2mp-loader": "^0.2.0",
@@ -31,6 +32,7 @@
"kebab-case": "^1.0.0",
"memory-fs": "^0.4.1",
"ora": "^3.4.0",
+ "terser": "^4.3.8",
"webpack": "^4.38.0",
"webpack-cli": "^3.3.6",
"webpack-merge": "^4.2.1"
diff --git a/packages/jsx2mp-cli/plugins/runtime.js b/packages/jsx2mp-cli/plugins/runtime.js
index 791d9e8342..dd46fe0ab8 100644
--- a/packages/jsx2mp-cli/plugins/runtime.js
+++ b/packages/jsx2mp-cli/plugins/runtime.js
@@ -1,5 +1,6 @@
const { join } = require('path');
-const { copySync, readJSONSync } = require('fs-extra');
+const { copySync, writeFileSync, readJSONSync, readFileSync } = require('fs-extra');
+const { minify } = require('terser');
/**
* Runtime packages should be a dependency of jsx2mp-cli,
@@ -11,8 +12,9 @@ const runtimePackageJSON = readJSONSync(runtimePackageJSONPath);
const runtimePackagePath = join(runtimePackageJSONPath, '..');
module.exports = class JSX2MPRuntimePlugin {
- constructor({ platform = 'ali' }) {
+ constructor({ platform = 'ali', mode = 'build' }) {
this.platform = platform;
+ this.mode = mode;
}
apply(compiler) {
@@ -25,7 +27,12 @@ module.exports = class JSX2MPRuntimePlugin {
const sourceFile = require.resolve(join(runtimePackagePath, runtimeTargetPath));
const targetFile = join(compiler.outputPath, 'npm', runtime + '.js');
- copySync(sourceFile, targetFile);
+ if (this.mode === 'build') {
+ const sourceCode = minify(readFileSync(sourceFile, 'utf-8')).code;
+ writeFileSync(targetFile, sourceCode);
+ } else {
+ copySync(sourceFile, targetFile);
+ }
callback();
}
);
diff --git a/packages/jsx2mp-loader/package.json b/packages/jsx2mp-loader/package.json
index 05b969bd04..1291506e94 100644
--- a/packages/jsx2mp-loader/package.json
+++ b/packages/jsx2mp-loader/package.json
@@ -1,6 +1,6 @@
{
"name": "jsx2mp-loader",
- "version": "0.2.2",
+ "version": "0.2.3",
"description": "",
"main": "src/app-loader.js",
"scripts": {
@@ -13,11 +13,16 @@
"@babel/plugin-proposal-class-properties": "^7.5.5",
"@babel/plugin-proposal-export-default-from": "^7.5.2",
"@babel/preset-env": "^7.5.5",
+ "babel-plugin-danger-remove-unused-import": "^2.0.0",
"babel-plugin-minify-dead-code-elimination": "^0.5.1",
"babel-plugin-transform-define": "^1.3.1",
"chalk": "^2.4.2",
+ "csso": "^3.5.1",
+ "convert-source-map": "^1.6.0",
"fs-extra": "^8.1.0",
"jsx-compiler": "^0.3.0",
- "loader-utils": "^1.2.3"
+ "loader-utils": "^1.2.3",
+ "pretty-data": "^0.40.0",
+ "terser": "^4.3.8"
}
}
diff --git a/packages/jsx2mp-loader/src/app-loader.js b/packages/jsx2mp-loader/src/app-loader.js
index ae955bf757..136c7e764a 100644
--- a/packages/jsx2mp-loader/src/app-loader.js
+++ b/packages/jsx2mp-loader/src/app-loader.js
@@ -1,10 +1,12 @@
-const { readJSONSync, writeJSONSync, writeFileSync, readFileSync, existsSync, mkdirSync } = require('fs-extra');
+const { readJSONSync, readFileSync, existsSync, mkdirSync } = require('fs-extra');
const { join } = require('path');
const compiler = require('jsx-compiler');
const { getOptions } = require('loader-utils');
const moduleResolve = require('./utils/moduleResolve');
const { removeExt } = require('./utils/pathHelper');
+const eliminateDeadCode = require('./utils/dce');
const defaultStyle = require('./defaultStyle');
+const output = require('./output');
function createImportStatement(req) {
@@ -32,7 +34,7 @@ function getRelativePath(filePath) {
module.exports = function appLoader(content) {
const loaderOptions = getOptions(this);
- const { entryPath, platform } = loaderOptions;
+ const { entryPath, platform, mode, disableCopyNpm } = loaderOptions;
const appConfigPath = removeExt(this.resourcePath) + '.json';
const rawContent = readFileSync(this.resourcePath, 'utf-8');
const config = readJSONSync(appConfigPath);
@@ -46,20 +48,36 @@ module.exports = function appLoader(content) {
resourcePath: this.resourcePath,
outputPath,
sourcePath,
+ platform,
type: 'app',
+ sourceFileName: this.resourcePath,
+ disableCopyNpm
});
- const transformed = compiler(rawContent, compilerOptions);
+ const rawContentAfterDCE = eliminateDeadCode(rawContent);
+ const transformed = compiler(rawContentAfterDCE, compilerOptions);
this.addDependency(appConfigPath);
const transformedAppConfig = transformAppConfig(entryPath, config);
- writeFileSync(join(outputPath, 'app.js'), transformed.code);
- writeJSONSync(join(outputPath, 'app.json'), transformedAppConfig, { spaces: 2 });
- // Write app.config.js for route information.
- writeFileSync(join(outputPath, 'app.config.js'), `module.exports = ${JSON.stringify(config, null, 2)}`);
- const appCssPath = join(outputPath, 'app' + platform.extension.css);
- const appCss = transformed.style ? defaultStyle + transformed.style : defaultStyle;
- writeFileSync(appCssPath, appCss);
+
+ const outputContent = {
+ code: transformed.code,
+ map: transformed.map,
+ config: `module.exports = ${JSON.stringify(config, null, 2)}`,
+ css: transformed.style ? defaultStyle + transformed.style : defaultStyle,
+ json: transformedAppConfig
+ };
+ const outputOption = {
+ outputPath: {
+ code: join(outputPath, 'app.js'),
+ json: join(outputPath, 'app.json'),
+ css: join(outputPath, 'app' + platform.extension.css),
+ config: join(outputPath, 'app.config.js')
+ },
+ mode
+ };
+
+ output(outputContent, rawContent, outputOption);
return [
`/* Generated by JSX2MP AppLoader, sourceFile: ${this.resourcePath}. */`,
@@ -77,9 +95,14 @@ function transformAppConfig(entryPath, originalConfig) {
const pages = [];
if (Array.isArray(value)) {
// Only resolve first level of routes.
- value.forEach(({ component, source }) => {
+ value.forEach(({ component, source, targets }) => {
// Compatible with old version definition of `component`.
- pages.push(moduleResolve(entryPath, getRelativePath(source || component)));
+ if (!Array.isArray(targets)) {
+ pages.push(moduleResolve(entryPath, getRelativePath(source || component)));
+ }
+ if (Array.isArray(targets) && targets.indexOf('miniapp') > -1) {
+ pages.push(moduleResolve(entryPath, getRelativePath(source || component)));
+ }
});
}
config.pages = pages;
diff --git a/packages/jsx2mp-loader/src/babel-plugin-rename-import.js b/packages/jsx2mp-loader/src/babel-plugin-rename-import.js
index 6325705212..cb9d2a832f 100644
--- a/packages/jsx2mp-loader/src/babel-plugin-rename-import.js
+++ b/packages/jsx2mp-loader/src/babel-plugin-rename-import.js
@@ -1,23 +1,20 @@
const { join, relative, dirname } = require('path');
-const { existsSync, statSync } = require('fs-extra');
const chalk = require('chalk');
const { isNpmModule, isWeexModule, isRaxModule } = require('./utils/judgeModule');
+const RUNTIME = 'jsx2mp-runtime';
+
+const getRuntimeByPlatform = (platform) => `${RUNTIME}/dist/jsx2mp-runtime.${platform}.esm`;
+
const defaultOptions = {
normalizeNpmFileName: (s) => s,
};
-function getNpmName(value) {
- const isScopedNpm = /^_?@/.test(value);
- return value.split('/').slice(0, isScopedNpm ? 2 : 1).join('/');
-}
-
module.exports = function visitor({ types: t }, options) {
options = Object.assign({}, defaultOptions, options);
- const { normalizeNpmFileName, nodeModulesPathList, distSourcePath, outputPath } = options;
+ const { normalizeNpmFileName, nodeModulesPathList, distSourcePath, outputPath, disableCopyNpm, platform } = options;
const source = (value, npmList, filename, rootContext) => {
- const npmName = getNpmName(value);
// Example:
// value => '@ali/universal-goldlog' or '@ali/xxx/foo/lib'
// npmList => ['/Users/xxx/node_modules/xxx', '/Users/xxx/node_modules/aaa/node_modules/bbb']
@@ -27,22 +24,9 @@ module.exports = function visitor({ types: t }, options) {
const searchPaths = npmList.reverse();
const target = require.resolve(value, { paths: searchPaths });
- // In tnpm, target will be like following (symbol linked path):
- // ***/_universal-toast_1.0.0_universal-toast/lib/index.js
- let packageJSONPath;
- try {
- packageJSONPath = require.resolve(join(npmName, 'package.json'), { paths: searchPaths });
- } catch (err) {
- throw new Error(`You may not have npm installed: "${npmName}"`);
- }
-
- const moduleBasePath = join(packageJSONPath, '..');
- const modulePathSuffix = relative(moduleBasePath, target);
- // ret => '../npm/_ali/universal-goldlog/lib/index.js
-
const rootNodeModulePath = join(rootContext, 'node_modules');
const filePath = relative(dirname(distSourcePath), join(outputPath, 'npm', relative(rootNodeModulePath, target)));
- return t.stringLiteral(normalizeNpmFileName(filePath));
+ return t.stringLiteral(normalizeNpmFileName('./' + filePath));
};
return {
@@ -52,9 +36,13 @@ module.exports = function visitor({ types: t }, options) {
if (isWeexModule(value)) {
path.remove();
} else if (isRaxModule(value)) {
- const rootNpmRelativePath = relative(dirname(distSourcePath), join(outputPath, 'npm'));
- path.node.source = t.stringLiteral('./' + join(rootNpmRelativePath, 'jsx2mp-runtime'));
- } else if (isNpmModule(value)) {
+ let runtimePath = getRuntimeByPlatform(platform.type);
+ if (!disableCopyNpm) {
+ const rootNpmRelativePath = relative(dirname(distSourcePath), join(outputPath, 'npm'));
+ runtimePath = './' + join(rootNpmRelativePath, RUNTIME);
+ }
+ path.node.source = t.stringLiteral(runtimePath);
+ } else if (isNpmModule(value) && !disableCopyNpm) {
path.node.source = source(value, nodeModulesPathList, state.filename, state.cwd);
}
},
@@ -67,11 +55,20 @@ module.exports = function visitor({ types: t }, options) {
node.arguments.length === 1
) {
if (t.isStringLiteral(node.arguments[0])) {
- if (isWeexModule(node.arguments[0].value)) {
- path.replaceWith(t.nullLiteral());
- } else if (isNpmModule(node.arguments[0].value)) {
+ const moduleName = node.arguments[0].value;
+ if (isWeexModule(moduleName)) {
+ path.remove();
+ } else if (isRaxModule(moduleName)) {
+ let runtimePath = t.stringLiteral(getRuntimeByPlatform(platform.type));
+ if (!disableCopyNpm) {
+ runtimePath = source(moduleName, nodeModulesPathList, state.filename, state.cwd);
+ }
+ path.node.arguments = [
+ runtimePath
+ ];
+ } else if (isNpmModule(moduleName) && !disableCopyNpm) {
path.node.arguments = [
- source(node.arguments[0].value, nodeModulesPathList, state.filename, state.cwd)
+ source(moduleName, nodeModulesPathList, state.filename, state.cwd)
];
}
} else if (t.isExpression(node.arguments[0])) {
diff --git a/packages/jsx2mp-loader/src/component-loader.js b/packages/jsx2mp-loader/src/component-loader.js
index 11a5d8b25b..4839648da2 100644
--- a/packages/jsx2mp-loader/src/component-loader.js
+++ b/packages/jsx2mp-loader/src/component-loader.js
@@ -1,17 +1,18 @@
-const { readJSONSync, writeJSONSync, writeFileSync, readFileSync, existsSync, mkdirpSync } = require('fs-extra');
-const { relative, join, dirname, sep } = require('path');
+const { readFileSync, existsSync, mkdirpSync } = require('fs-extra');
+const { relative, join, dirname, sep, extname } = require('path');
const compiler = require('jsx-compiler');
const { getOptions } = require('loader-utils');
const cached = require('./cached');
const { removeExt } = require('./utils/pathHelper');
-
+const eliminateDeadCode = require('./utils/dce');
+const output = require('./output');
const ComponentLoader = __filename;
module.exports = function componentLoader(content) {
const loaderOptions = getOptions(this);
- const { platform, entryPath, constantDir } = loaderOptions;
+ const { platform, entryPath, constantDir, mode, disableCopyNpm } = loaderOptions;
const rawContent = readFileSync(this.resourcePath, 'utf-8');
const resourcePath = this.resourcePath;
const rootContext = this.rootContext;
@@ -35,10 +36,13 @@ module.exports = function componentLoader(content) {
outputPath,
sourcePath,
type: 'component',
- platform
+ platform,
+ sourceFileName: this.resourcePath,
+ disableCopyNpm
});
- const transformed = compiler(rawContent, compilerOptions);
+ const rawContentAfterDCE = eliminateDeadCode(rawContent);
+ const transformed = compiler(rawContentAfterDCE, compilerOptions);
const config = Object.assign({}, transformed.config);
if (Array.isArray(transformed.dependencies)) {
@@ -65,25 +69,27 @@ module.exports = function componentLoader(content) {
const distFileDir = dirname(distFileWithoutExt);
if (!existsSync(distFileDir)) mkdirpSync(distFileDir);
- // Write code
- writeFileSync(distFileWithoutExt + '.js', transformed.code);
- // Write template
- writeFileSync(distFileWithoutExt + platform.extension.xml, transformed.template);
- // Write config
- writeJSONSync(distFileWithoutExt + '.json', config, { spaces: 2 });
- // Write acss style
- if (transformed.style) {
- writeFileSync(distFileWithoutExt + platform.extension.css, transformed.style);
- }
- // Write extra assets
- if (transformed.assets) {
- Object.keys(transformed.assets).forEach((asset) => {
- const content = transformed.assets[asset];
- const assetDirectory = dirname(join(outputPath, asset));
- if (!existsSync(assetDirectory)) mkdirpSync(assetDirectory);
- writeFileSync(join(outputPath, asset), content);
- });
- }
+
+ const outputContent = {
+ code: transformed.code,
+ map: transformed.map,
+ css: transformed.style || '',
+ json: config,
+ template: transformed.template,
+ assets: transformed.assets
+ };
+ const outputOption = {
+ outputPath: {
+ code: distFileWithoutExt + '.js',
+ json: distFileWithoutExt + '.json',
+ css: distFileWithoutExt + platform.extension.css,
+ template: distFileWithoutExt + platform.extension.xml,
+ assets: outputPath
+ },
+ mode
+ };
+
+ output(outputContent, rawContent, outputOption);
function isCustomComponent(name, usingComponents = {}) {
const matchingPath = join(dirname(resourcePath), name);
@@ -102,7 +108,11 @@ module.exports = function componentLoader(content) {
const denpendencies = [];
Object.keys(transformed.imported).forEach(name => {
if (isCustomComponent(name, transformed.usingComponents)) {
- denpendencies.push({ name, loader: ComponentLoader, options: { entryPath: loaderOptions.entryPath, platform: loaderOptions.platform, constantDir: loaderOptions.constantDir } });
+ denpendencies.push({
+ name,
+ loader: ComponentLoader,
+ options: loaderOptions
+ });
} else {
denpendencies.push({ name });
}
diff --git a/packages/jsx2mp-loader/src/output.js b/packages/jsx2mp-loader/src/output.js
new file mode 100644
index 0000000000..ec567602ec
--- /dev/null
+++ b/packages/jsx2mp-loader/src/output.js
@@ -0,0 +1,121 @@
+const { writeJSONSync, writeFileSync, existsSync, mkdirpSync } = require('fs-extra');
+const { extname, dirname, join } = require('path');
+const { transformSync } = require('@babel/core');
+const { minify, minifyJS, minifyCSS, minifyXML } = require('./utils/minifyCode');
+const addSourceMap = require('./utils/addSourceMap');
+
+function transformCode(rawContent, mode, externalPlugins = [], externalPreset = []) {
+ const presets = [].concat(externalPreset);
+ const plugins = externalPlugins.concat([
+ require('@babel/plugin-proposal-export-default-from'), // for support of export defualt
+ [
+ require('babel-plugin-transform-define'),
+ {
+ 'process.env.NODE_ENV': mode === 'build' ? 'production' : 'development',
+ }
+ ],
+ [
+ require('babel-plugin-minify-dead-code-elimination'),
+ {
+ optimizeRawSize: true,
+ keepFnName: true
+ }
+ ],
+ ]);
+
+
+ const babelParserOption = {
+ plugins: [
+ 'classProperties',
+ 'jsx',
+ 'flow',
+ 'flowComment',
+ 'trailingFunctionCommas',
+ 'asyncFunctions',
+ 'exponentiationOperator',
+ 'asyncGenerators',
+ 'objectRestSpread',
+ ['decorators', { decoratorsBeforeExport: false }],
+ 'dynamicImport',
+ ], // support all plugins
+ };
+
+ return transformSync(rawContent, {
+ presets,
+ plugins,
+ parserOpts: babelParserOption
+ });
+}
+
+/**
+ * Process and write file
+ * @param {object} content Compiled result
+ * @param {string} raw Original file content
+ * @param {object} options
+ */
+function output(content, raw, options) {
+ const { mode, outputPath, externalPlugins = [] } = options;
+ let { code, config, json, css, map, template, assets } = content;
+ if (mode === 'build') {
+ // Compile ES6 => ES5 and minify code
+ code = minifyJS(transformCode(code,
+ mode,
+ externalPlugins.concat([require('@babel/plugin-proposal-class-properties')]),
+ [require('@babel/preset-env')]
+ ).code);
+ if (config) {
+ // Compile ES6 => ES5 and minify code
+ config = minifyJS(transformCode(config,
+ mode,
+ externalPlugins.concat([require('@babel/plugin-proposal-class-properties')]),
+ [require('@babel/preset-env')]
+ ).code);
+ }
+ if (css) {
+ css = minifyCSS(css);
+ }
+ if (template) {
+ template = minifyXML(template);
+ }
+ } else {
+ code = transformCode(code, mode, externalPlugins).code;
+ // Add source map
+ if (map) {
+ code = addSourceMap(code, raw, map);
+ }
+ }
+
+ // Write file
+ writeFileSync(outputPath.code, code);
+ if (json) {
+ writeJSONSync(outputPath.json, json, { spaces: 2});
+ }
+ if (template) {
+ writeFileSync(outputPath.template, template);
+ }
+ if (css) {
+ writeFileSync(outputPath.css, css);
+ }
+ if (config) {
+ writeFileSync(outputPath.config, config);
+ }
+
+ // Write extra assets
+ if (assets) {
+ Object.keys(assets).forEach((asset) => {
+ const ext = extname(asset);
+ let content = assets[asset];
+ if (mode === 'build') {
+ content = minify(content, ext);
+ }
+ const assetsOutputPath = join(outputPath.assets, asset);
+ const assetDirectory = dirname(assetsOutputPath);
+ if (!existsSync(assetDirectory)) {
+ mkdirpSync(assetDirectory);
+ }
+ writeFileSync(assetsOutputPath, content);
+ });
+ }
+}
+
+module.exports = output;
diff --git a/packages/jsx2mp-loader/src/page-loader.js b/packages/jsx2mp-loader/src/page-loader.js
index 9113c8ec99..1c4b31a80f 100644
--- a/packages/jsx2mp-loader/src/page-loader.js
+++ b/packages/jsx2mp-loader/src/page-loader.js
@@ -1,14 +1,16 @@
const { readJSONSync, writeJSONSync, writeFileSync, readFileSync, existsSync, mkdirpSync } = require('fs-extra');
-const { relative, join, dirname } = require('path');
+const { relative, join, dirname, extname } = require('path');
const { getOptions } = require('loader-utils');
const compiler = require('jsx-compiler');
const { removeExt } = require('./utils/pathHelper');
+const eliminateDeadCode = require('./utils/dce');
+const output = require('./output');
const ComponentLoader = require.resolve('./component-loader');
module.exports = function pageLoader(content) {
const loaderOptions = getOptions(this);
- const { platform, entryPath } = loaderOptions;
+ const { platform, entryPath, mode, disableCopyNpm } = loaderOptions;
const rawContent = readFileSync(this.resourcePath, 'utf-8');
const resourcePath = this.resourcePath;
@@ -22,11 +24,13 @@ module.exports = function pageLoader(content) {
outputPath,
sourcePath,
type: 'page',
- platform
+ platform,
+ sourceFileName: this.resourcePath,
+ disableCopyNpm
});
-
- const transformed = compiler(rawContent, compilerOptions);
+ const rawContentAfterDCE = eliminateDeadCode(rawContent);
+ const transformed = compiler(rawContentAfterDCE, compilerOptions);
const pageDistDir = dirname(targetFilePath);
if (!existsSync(pageDistDir)) mkdirpSync(pageDistDir);
@@ -54,25 +58,26 @@ module.exports = function pageLoader(content) {
config.usingComponents = usingComponents;
}
- // Write js content
- writeFileSync(distFileWithoutExt + '.js', transformed.code);
- // Write template
- writeFileSync(distFileWithoutExt + platform.extension.xml, transformed.template);
- // Write config
- writeJSONSync(distFileWithoutExt + '.json', config, { spaces: 2 });
- // Write acss style
- if (transformed.style) {
- writeFileSync(distFileWithoutExt + platform.extension.css, transformed.style);
- }
- // Write extra assets
- if (transformed.assets) {
- Object.keys(transformed.assets).forEach((asset) => {
- const content = transformed.assets[asset];
- const assetDirectory = dirname(join(outputPath, asset));
- if (!existsSync(assetDirectory)) mkdirpSync(assetDirectory);
- writeFileSync(join(outputPath, asset), content);
- });
- }
+ const outputContent = {
+ code: transformed.code,
+ map: transformed.map,
+ css: transformed.style || '',
+ json: config,
+ template: transformed.template,
+ assets: transformed.assets
+ };
+ const outputOption = {
+ outputPath: {
+ code: distFileWithoutExt + '.js',
+ json: distFileWithoutExt + '.json',
+ css: distFileWithoutExt + platform.extension.css,
+ template: distFileWithoutExt + platform.extension.xml,
+ assets: outputPath
+ },
+ mode
+ };
+
+ output(outputContent, rawContent, outputOption);
function isCustomComponent(name, usingComponents = {}) {
const matchingPath = join(dirname(resourcePath), name);
@@ -88,7 +93,11 @@ module.exports = function pageLoader(content) {
const denpendencies = [];
Object.keys(transformed.imported).forEach(name => {
if (isCustomComponent(name, transformed.usingComponents)) {
- denpendencies.push({ name, loader: ComponentLoader, options: { entryPath: loaderOptions.entryPath, platform: loaderOptions.platform, constantDir: loaderOptions.constantDir } });
+ denpendencies.push({
+ name,
+ loader: ComponentLoader,
+ options: loaderOptions
+ });
} else {
denpendencies.push({ name });
}
diff --git a/packages/jsx2mp-loader/src/script-loader.js b/packages/jsx2mp-loader/src/script-loader.js
index 9e6513575b..193e23d140 100644
--- a/packages/jsx2mp-loader/src/script-loader.js
+++ b/packages/jsx2mp-loader/src/script-loader.js
@@ -1,12 +1,12 @@
const { join, dirname, relative } = require('path');
-const { copySync, lstatSync, existsSync, mkdirpSync, writeJSONSync, writeFileSync, readFileSync, readJSONSync } = require('fs-extra');
+const { copySync, existsSync, mkdirpSync, writeJSONSync, writeFileSync, readFileSync, readJSONSync } = require('fs-extra');
const { transformSync } = require('@babel/core');
const { getOptions } = require('loader-utils');
const cached = require('./cached');
const isMiniappComponent = require('./utils/isMiniappComponent');
const { removeExt } = require('./utils/pathHelper');
const { isNpmModule } = require('./utils/judgeModule');
-
+const output = require('./output');
const AppLoader = require.resolve('./app-loader');
const PageLoader = require.resolve('./page-loader');
@@ -21,6 +21,7 @@ module.exports = function scriptLoader(content) {
if (loaderHandled) return;
const loaderOptions = getOptions(this);
+ const { disableCopyNpm, mode, entryPath, platform } = loaderOptions;
const rawContent = readFileSync(this.resourcePath, 'utf-8');
const rootContext = this.rootContext;
const nodeModulesPathList = getNearestNodeModulesPath(rootContext, this.resourcePath);
@@ -39,6 +40,9 @@ module.exports = function scriptLoader(content) {
});
if (isFromNodeModule(this.resourcePath)) {
+ if (disableCopyNpm) {
+ return '';
+ }
const relativeNpmPath = relative(currentNodeModulePath, this.resourcePath);
const npmFolderName = getNpmFolderName(relativeNpmPath);
const sourcePackagePath = join(currentNodeModulePath, npmFolderName);
@@ -91,96 +95,53 @@ module.exports = function scriptLoader(content) {
splitedNpmPath.shift(); // Skip npm module package, for cnpm/tnpm will rewrite this.
const distSourcePath = normalizeNpmFileName(join(outputPath, 'npm', relative(rootNodeModulePath, this.resourcePath)));
- const { code, map } = transformCode({rawContent, mode: loaderOptions.mode, nodeModulesPathList, relativeResourcePath, distSourcePath, outputPath});
-
const distSourceDirPath = dirname(distSourcePath);
if (!existsSync(distSourceDirPath)) mkdirpSync(distSourceDirPath);
- writeFileSync(distSourcePath, code, 'utf-8');
- writeJSONSync(distSourcePath + '.map', map);
+
+ const outputContent = { code: rawContent };
+ const outputOption = {
+ outputPath: {
+ code: distSourcePath
+ },
+ mode,
+ externalPlugins: [
+ [
+ require('./babel-plugin-rename-import'),
+ { normalizeNpmFileName, nodeModulesPathList, distSourcePath, outputPath, disableCopyNpm, platform }
+ ]
+ ]
+ };
+ output(outputContent, null, outputOption);
}
} else {
const relativeFilePath = relative(
- join(rootContext, dirname(loaderOptions.entryPath)),
+ join(rootContext, dirname(entryPath)),
this.resourcePath
);
const distSourcePath = join(outputPath, relativeFilePath);
const distSourceDirPath = dirname(distSourcePath);
- const { code } = transformCode({rawContent, mode: loaderOptions.mode, nodeModulesPathList, relativeResourcePath, distSourcePath, outputPath});
-
if (!existsSync(distSourceDirPath)) mkdirpSync(distSourceDirPath);
- writeFileSync(distSourcePath, code, 'utf-8');
+ const outputContent = { code: rawContent };
+ const outputOption = {
+ outputPath: {
+ code: distSourcePath
+ },
+ mode,
+ externalPlugins: [
+ [
+ require('./babel-plugin-rename-import'),
+ { normalizeNpmFileName, nodeModulesPathList, distSourcePath, outputPath, disableCopyNpm, platform }
+ ]
+ ]
+ };
+
+ output(outputContent, null, outputOption);
}
return content;
};
-/**
- *
- * @param {object} option
- * @param {string} option.rawContent code to be transformed
- * @param {string} option.mode transform mode, build or watch
- * @param {array} option.nodeModulesPathList existed node_modules paths
- * @param {array} option.relativeResourcePath current file path relative to rootContext
- * @param {array} option.distSourcePath file path that transformed to
- * @param {array} option.outputPath dist dir path
- *
- */
-function transformCode({rawContent, mode, nodeModulesPathList = [], relativeResourcePath, distSourcePath, outputPath}) {
- const presets = [];
- const plugins = [
- [
- require('./babel-plugin-rename-import'),
- { normalizeNpmFileName, nodeModulesPathList, distSourcePath, outputPath }
-
- ], // for rename npm modules.
- require('@babel/plugin-proposal-export-default-from'), // for support of export defualt
- [
- require('babel-plugin-transform-define'),
- {
- 'process.env.NODE_ENV': mode === 'build' ? 'production' : 'development',
- }
- ],
- [
- require('babel-plugin-minify-dead-code-elimination'),
- {
- optimizeRawSize: true,
- keepFnName: true
- }
- ]
- ];
-
- // Compile to ES5 for build.
- if (mode === 'build') {
- presets.push(require('@babel/preset-env'));
- plugins.push(require('@babel/plugin-proposal-class-properties'));
- }
-
- const babelParserOption = {
- plugins: [
- 'classProperties',
- 'jsx',
- 'flow',
- 'flowComment',
- 'trailingFunctionCommas',
- 'asyncFunctions',
- 'exponentiationOperator',
- 'asyncGenerators',
- 'objectRestSpread',
- ['decorators', { decoratorsBeforeExport: false }],
- 'dynamicImport',
- ], // support all plugins
- };
-
- return transformSync(rawContent, {
- presets,
- plugins,
- filename: relativeResourcePath,
- parserOpts: babelParserOption,
- sourceMaps: true
- });
-}
-
/**
* For that alipay build folder can not contain `@`, escape to `_`.
*/
diff --git a/packages/jsx2mp-loader/src/utils/addSourceMap.js b/packages/jsx2mp-loader/src/utils/addSourceMap.js
new file mode 100644
index 0000000000..8092eba843
--- /dev/null
+++ b/packages/jsx2mp-loader/src/utils/addSourceMap.js
@@ -0,0 +1,11 @@
+const convertSourceMap = require('convert-source-map');
+
+function addSourceMap(code, rawCode, originalMap) {
+ const map = Object.assign(originalMap, {
+ sourcesContent: [rawCode]
+ });
+ const sourceMapString = convertSourceMap.fromObject(map).toComment();
+ return code + '\n' + sourceMapString;
+}
+
+module.exports = addSourceMap;
diff --git a/packages/jsx2mp-loader/src/utils/dce.js b/packages/jsx2mp-loader/src/utils/dce.js
new file mode 100644
index 0000000000..870729d35e
--- /dev/null
+++ b/packages/jsx2mp-loader/src/utils/dce.js
@@ -0,0 +1,40 @@
+const { transformSync } = require('@babel/core');
+
+const parserOpts = {
+ plugins: [
+ 'classProperties',
+ 'jsx',
+ 'flow',
+ 'flowComment',
+ 'trailingFunctionCommas',
+ 'asyncFunctions',
+ 'exponentiationOperator',
+ 'asyncGenerators',
+ 'objectRestSpread',
+ ['decorators', { decoratorsBeforeExport: false }],
+ 'dynamicImport',
+ ], // support all plugins
+};
+
+function eliminateDeadCode(source) {
+ return transformSync(source, {
+ parserOpts,
+ plugins: [
+ [
+ require('babel-plugin-minify-dead-code-elimination'),
+ {
+ optimizeRawSize: true,
+ keepFnName: true
+ }
+ ],
+ [
+ require('babel-plugin-danger-remove-unused-import'),
+ {
+ ignore: 'rax'
+ }
+ ]
+ ]
+ }).code;
+}
+
+module.exports = eliminateDeadCode;
diff --git a/packages/jsx2mp-loader/src/utils/minifyCode.js b/packages/jsx2mp-loader/src/utils/minifyCode.js
new file mode 100644
index 0000000000..4aa9da61c2
--- /dev/null
+++ b/packages/jsx2mp-loader/src/utils/minifyCode.js
@@ -0,0 +1,46 @@
+const terser = require('terser');
+const csso = require('csso');
+const prettyData = require('pretty-data').pd;
+
+function minifyJS(source) {
+ return terser.minify(source).code;
+}
+
+function minifyCSS(source) {
+ return csso.minify(source, {
+ restructure: false
+ }).css;
+}
+
+function minifyXML(source) {
+ return prettyData.xmlmin(source);
+}
+
+function minifyJSON(source) {
+ return prettyData.json(source);
+}
+
+function minify(source, type = '.js') {
+ if (type === '.js') {
+ return minifyJS(source);
+ }
+ if (type === '.css') {
+ return minifyCSS(source);
+ }
+ if (type === '.json') {
+ return minifyJSON(source);
+ }
+ if (/\..*ml/.test(type)) {
+ return minifyXML(source);
+ }
+
+ return source;
+}
+
+module.exports = {
+ minify,
+ minifyJS,
+ minifyCSS,
+ minifyXML,
+ minifyJSON
+};
diff --git a/packages/jsx2mp-runtime/package.json b/packages/jsx2mp-runtime/package.json
index a9bc21958b..f2858047ad 100644
--- a/packages/jsx2mp-runtime/package.json
+++ b/packages/jsx2mp-runtime/package.json
@@ -1,6 +1,6 @@
{
"name": "jsx2mp-runtime",
- "version": "0.3.1",
+ "version": "0.3.2",
"description": "Runtime for jsx2mp.",
"miniprogram": {
"ali": "dist/jsx2mp-runtime.ali.esm.js",
diff --git a/packages/jsx2mp-runtime/src/bridge.js b/packages/jsx2mp-runtime/src/bridge.js
index 474e597fa1..3acd265320 100644
--- a/packages/jsx2mp-runtime/src/bridge.js
+++ b/packages/jsx2mp-runtime/src/bridge.js
@@ -2,7 +2,7 @@
import { cycles as appCycles } from './app';
import Component from './component';
import { ON_SHOW, ON_HIDE, ON_PAGE_SCROLL, ON_SHARE_APP_MESSAGE, ON_REACH_BOTTOM, ON_PULL_DOWN_REFRESH } from './cycles';
-import { setComponentInstance, getComponentProps, executeCallbacks } from './updater';
+import { setComponentInstance, getComponentProps } from './updater';
import { getComponentLifecycle } from '@@ADAPTER@@';
import { createMiniAppHistory } from './history';
import { __updateRouterMap } from './router';
@@ -68,7 +68,6 @@ function getComponentCycles(Klass) {
if (this[PROPS].hasOwnProperty('__tagId')) {
const componentId = this[PROPS].__tagId;
setComponentInstance(componentId, this.instance);
- executeCallbacks();
}
if (GET_DERIVED_STATE_FROM_PROPS in Klass) {
diff --git a/packages/jsx2mp-runtime/src/component.js b/packages/jsx2mp-runtime/src/component.js
index ce72d29797..ad865215c7 100644
--- a/packages/jsx2mp-runtime/src/component.js
+++ b/packages/jsx2mp-runtime/src/component.js
@@ -30,6 +30,7 @@ export default class Component {
this.__dependencies = {}; // for context
+ this.__mounted = false;
this.__shouldUpdate = false;
this._methods = {};
this._hooks = {};
@@ -99,6 +100,24 @@ export default class Component {
updateChildProps(this, chlidInstanceId, props);
}
+ _registerRefs(refs) {
+ this.refs = {};
+ refs.forEach(({name, method}) => {
+ if (!method) {
+ const target = {
+ current: null
+ };
+ this._internal[name] = ref => {
+ target.current = ref;
+ };
+ this.refs[name] = target;
+ } else {
+ this._internal[name] = method;
+ this.refs[name] = method;
+ }
+ });
+ }
+
_collectState() {
const state = Object.assign({}, this.state);
let parialState;
@@ -166,7 +185,6 @@ export default class Component {
// Step4: mark __mounted = true
if (!this.__mounted) {
this.__mounted = true;
-
// Step5: trigger did mount
this._trigger(COMPONENT_DID_MOUNT);
}
@@ -178,12 +196,11 @@ export default class Component {
_updateComponent() {
if (!this.__mounted) return;
-
// Step1: propTypes check, now skipped.
// Step2: make props to prevProps, and trigger willReceiveProps
const nextProps = this.props; // actually this is nextProps
const prevProps = this.props = this.prevProps || this.props;
- if (this.__mounted && diffProps(prevProps, nextProps)) {
+ if (diffProps(prevProps, nextProps)) {
this._trigger(COMPONENT_WILL_RECEIVE_PROPS, this.props);
}
@@ -202,18 +219,16 @@ export default class Component {
if (stateFromProps !== undefined) nextState = stateFromProps;
// Step5: judge shouldComponentUpdate
- if (this.__mounted) {
- this.__shouldUpdate = true;
- if (
- !this.__forceUpdate
- && this.shouldComponentUpdate
- && this.shouldComponentUpdate(nextProps, nextState) === false
- ) {
- this.__shouldUpdate = false;
- } else {
- // Step6: trigger will update
- this._trigger(COMPONENT_WILL_UPDATE, nextProps, nextState);
- }
+ this.__shouldUpdate = true;
+ if (
+ !this.__forceUpdate
+ && this.shouldComponentUpdate
+ && this.shouldComponentUpdate(nextProps, nextState) === false
+ ) {
+ this.__shouldUpdate = false;
+ } else {
+ // Step6: trigger will update
+ this._trigger(COMPONENT_WILL_UPDATE, nextProps, nextState);
}
this.props = nextProps;
diff --git a/packages/jsx2mp-runtime/src/enqueueRender.js b/packages/jsx2mp-runtime/src/enqueueRender.js
index 503df46808..6b6868ebd6 100644
--- a/packages/jsx2mp-runtime/src/enqueueRender.js
+++ b/packages/jsx2mp-runtime/src/enqueueRender.js
@@ -1,5 +1,4 @@
import nextTick from './nextTick';
-import { RENDER } from './cycles';
let queue = [];
diff --git a/packages/jsx2mp-runtime/src/hooks.js b/packages/jsx2mp-runtime/src/hooks.js
index ef3ed8df8e..dd1c739a65 100644
--- a/packages/jsx2mp-runtime/src/hooks.js
+++ b/packages/jsx2mp-runtime/src/hooks.js
@@ -177,9 +177,15 @@ export function useRef(initialValue) {
const hooks = currentInstance.getHooks();
if (!hooks[hookID]) {
- hooks[hookID] = {
+ const ref = {
current: initialValue
};
+ const refFn = (instance) => {
+ ref.current = instance;
+ };
+ refFn.__proto__ = ref;
+ // currentInstance._internal.,
+ hooks[hookID] = refFn;
}
return hooks[hookID];
diff --git a/packages/jsx2mp-runtime/src/updater.js b/packages/jsx2mp-runtime/src/updater.js
index b3122fb3c7..be23a98024 100644
--- a/packages/jsx2mp-runtime/src/updater.js
+++ b/packages/jsx2mp-runtime/src/updater.js
@@ -1,14 +1,20 @@
import nextTick from './nextTick';
+import { enqueueRender } from './enqueueRender';
const propsMap = {
// tagId -> props
};
const componentIntances = {};
-const updateChildPropsCallbacks = [];
+const updateChildPropsCallbacks = {};
export function setComponentInstance(instanceId, instance) {
componentIntances[instanceId] = instance;
+ // Check component should update chlid props
+ if (updateChildPropsCallbacks[instanceId]) {
+ updateChildPropsCallbacks[instanceId]();
+ updateChildPropsCallbacks[instanceId] = null;
+ }
}
export function getComponentProps(tagId) {
@@ -23,8 +29,8 @@ export function removeComponentProps(tagId) {
}
export function updateChildProps(trigger, instanceId, nextProps) {
- const targetComponent = componentIntances[instanceId];
if (trigger) {
+ const targetComponent = componentIntances[instanceId];
// Create a new object reference.
if (targetComponent) {
propsMap[instanceId] = Object.assign(
@@ -33,21 +39,19 @@ export function updateChildProps(trigger, instanceId, nextProps) {
nextProps,
);
nextTick(() => {
- Object.assign(targetComponent.props, propsMap[instanceId]);
- targetComponent._updateComponent();
+ targetComponent.props = propsMap[instanceId];
+ enqueueRender(targetComponent);
});
} else {
/**
* updateChildProps may execute before setComponentInstance
*/
- updateChildPropsCallbacks.push(
- updateChildProps.bind(null, trigger, instanceId, nextProps),
+ updateChildPropsCallbacks[instanceId] = updateChildProps.bind(
+ null,
+ trigger,
+ instanceId,
+ nextProps,
);
}
}
}
-
-export function executeCallbacks() {
- updateChildPropsCallbacks.forEach(callback => callback());
- updateChildPropsCallbacks.length = 0;
-}