Skip to content

Commit

Permalink
[RELEASE] JSX2MP @ 1024 (#1423)
Browse files Browse the repository at this point in the history
* chore: bump jsx2mp-loader version

* feat: generating source maps (#1433)

* refactor: generate inline source map in npm file

* feat: add source map for rax code in watch mode

* fix: add default sourceFileName to avoid test failure

* refactor: use inline source map to support alipay IDE

* fix: fix lint error

* chore: remove useless import

* feat: miniapp optimize build mode (#1434)

* feat: clean dist dir before generating

* feat: uglify runtime in build mode

* fix: lint error

* perf: minify js/css in build mode

* feat: minify Code in build mode and do dce to all rax code

* test: update compile result test case

* fix: lint error

* refactor: extract output logic to a single file

* fix: copy path

* fix: path resolve error in usingComponents

* fix: path error in script when using node modules

* fix: child component render

* fix: child component render

* chore: remove useless code

* feat: support logical expression with jsx (#1424)

* feat: support logical expression with jsx

* fix: nested logical expression

* chore: optimize warning

* feat: support useRef

* feat: add disable copy npm mode

* chore: remove useless code

* chore: add refs.length is 0 condition

* feat: support ref is string

* fix: fragment missing

* refactor: extract transformCode to output function

* fix: lint error

* test: update test case of generating correct js code

* feat: support build differentiated platform (#1442)

* chore: opitimize code

* feat: disable copy npm (#1441)

* feat: add disable copy npm mode

* refactor: extract transformCode to output function

* fix: lint error

* test: update test case of generating correct js code

* chore: opitimize code

* chore: bump version
  • Loading branch information
ChrisCindy authored and SoloJiang committed Oct 24, 2019
1 parent 9f9a7bf commit e2617a4
Show file tree
Hide file tree
Showing 36 changed files with 785 additions and 326 deletions.
3 changes: 2 additions & 1 deletion packages/jsx-compiler/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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": {
Expand Down
5 changes: 4 additions & 1 deletion packages/jsx-compiler/src/codegen/genCode.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
const generate = require('@babel/generator').default;

const generateOptions = {};
const generateOptions = {
sourceFileName: '',
sourceMaps: true
};

/**
* Generate code and map from babel ast.
Expand Down
2 changes: 1 addition & 1 deletion packages/jsx-compiler/src/codegen/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
27 changes: 27 additions & 0 deletions packages/jsx-compiler/src/modules/__tests__/attribute.js
Original file line number Diff line number Diff line change
@@ -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 = '<View key={1}>test</View>';
const ast = parseExpression(code);
_transformAttribute(ast, code, adapter);
expect(genCode(ast).code).toEqual('<View a:key={1}>test</View>');
});
it('should transform attribute name is className', () => {
const code = '<View className="box">test</View>';
const ast = parseExpression(code);
_transformAttribute(ast, code, adapter);
expect(genCode(ast).code).toEqual('<View class="box">test</View>');
});
it("should collect attribute name is ref and parse it's value as a string", () => {
const code = '<View ref={scrollViewRef}>test</View>';
const ast = parseExpression(code);
const refs = _transformAttribute(ast, code, adapter);
expect(genCode(ast).code).toEqual('<View ref="scrollViewRef">test</View>');
expect(refs).toEqual([t.stringLiteral('scrollViewRef')]);
});
});
28 changes: 26 additions & 2 deletions packages/jsx-compiler/src/modules/__tests__/condition.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ describe('Transform condition', () => {
const dynamicValue = _transformTemplate(ast, adapter, {});

expect(genCode(ast).code).toEqual('<View><block a:if="{{foo}}"><block a:if="{{bar}}"><Bar /></block><block a:else><View /></block></block><block a:else><Text /></block></View>');
expect(genDynamicValue(dynamicValue)).toEqual('{ foo: foo, bar: bar }');
expect(genDynamicValue(dynamicValue)).toEqual('{ bar: bar, foo: foo }');
});

it("transform condition's alternate is conditional expression", () => {
Expand All @@ -49,7 +49,7 @@ describe('Transform condition', () => {
const dynamicValue = _transformTemplate(ast, adapter, {});

expect(genCode(ast).code).toEqual('<View><block a:if="{{empty}}"><Empty /></block><block a:else><block a:if="{{loading}}"></block><block a:else>xxx</block></block></View>');
expect(genDynamicValue(dynamicValue)).toEqual('{ empty: empty, loading: loading }');
expect(genDynamicValue(dynamicValue)).toEqual('{ loading: loading, empty: empty }');
});

it('skip list dynamic value', () => {
Expand Down Expand Up @@ -88,6 +88,30 @@ describe('Transform condition', () => {
</View>`);
expect(genDynamicValue(dynamicValue)).toEqual('{ tabList: tabList }');
});

it('transform simple logical expression', () => {
const ast = parseExpression(`
<View>
{ a && <View>1</View>}
</View>
`);
_transformTemplate(ast, adapter, {});
expect(genCode(ast).code).toEqual(`<View>
<block a:if="{{a}}"><View>1</View></block><block a:else>{a}</block>
</View>`);
});

it('transform nested logical expression', () => {
const ast = parseExpression(`
<View>
{ a || b && <View>1</View>}
</View>
`);
_transformTemplate(ast, adapter, {});
expect(genCode(ast).code).toEqual(`<View>
<block a:if={!a}><block a:if="{{b}}"><View>1</View></block><block a:else>{b}</block></block><block a:else>{a}</block>
</View>`);
});
});

describe('Transiform condition render function', () => {
Expand Down
9 changes: 0 additions & 9 deletions packages/jsx-compiler/src/modules/__tests__/element.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,15 +109,6 @@ describe('Transform JSXElement', () => {
expect(genInlineCode(ast).code).toEqual('<View>{{ _d0 ? _d0.b[_d1.d] : 1 }}</View>');
expect(genDynamicAttrs(dynamicValues)).toEqual('{ _d0: a, _d1: c }');
});

it('should adapt attribute key', () => {
const sourceCode = '<View key={\'key\'}>{ bar }</View>';
const ast = parseExpression(sourceCode);
const { dynamicValues } = _transform(ast, null, adapter, sourceCode);
const code = genInlineCode(ast).code;
expect(code).toEqual('<View a:key=\'key\'>{{ _d0 }}</View>');
expect(genDynamicAttrs(dynamicValues)).toEqual('{ _d0: bar }');
});
});

describe('event handlers', () => {
Expand Down
44 changes: 44 additions & 0 deletions packages/jsx-compiler/src/modules/attribute.js
Original file line number Diff line number Diff line change
@@ -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,
};
106 changes: 79 additions & 27 deletions packages/jsx-compiler/src/modules/code.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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';
Expand Down Expand Up @@ -91,28 +91,30 @@ 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);

/**
* 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++) {
Expand Down Expand Up @@ -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);
}
},
};
Expand All @@ -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));
}
}
});
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
)
);

Expand Down Expand Up @@ -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;
}
}
Expand Down Expand Up @@ -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 `_`.
*/
Expand Down
Loading

0 comments on commit e2617a4

Please sign in to comment.