Skip to content

Commit e2617a4

Browse files
ChrisCindySoloJiang
authored andcommitted
[RELEASE] JSX2MP @ 1024 (#1423)
* 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
1 parent 9f9a7bf commit e2617a4

36 files changed

+785
-326
lines changed

packages/jsx-compiler/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "jsx-compiler",
3-
"version": "0.3.1",
3+
"version": "0.3.2",
44
"license": "BSD-3-Clause",
55
"description": "Parser for Rax JSX Statements.",
66
"main": "src/index.js",
@@ -25,6 +25,7 @@
2525
"fs-extra": "^7.0.1",
2626
"kebab-case": "^1.0.0",
2727
"md5": "^2.2.1",
28+
"resolve": "^1.12.0",
2829
"stylesheet-loader": "^0.6.6-0"
2930
},
3031
"devDependencies": {

packages/jsx-compiler/src/codegen/genCode.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
const generate = require('@babel/generator').default;
22

3-
const generateOptions = {};
3+
const generateOptions = {
4+
sourceFileName: '',
5+
sourceMaps: true
6+
};
47

58
/**
69
* Generate code and map from babel ast.

packages/jsx-compiler/src/codegen/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ const genCode = require('./genCode');
33
const { baseOptions } = require('../options');
44

55
function generate(parsed, options = baseOptions) {
6-
const { code, map } = genCode(parsed.ast);
6+
const { code, map } = genCode(parsed.ast, options);
77
const ret = {
88
code, map,
99
// config, template, style and others should be generated in plugin modules.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
const t = require('@babel/types');
2+
const { _transformAttribute } = require('../attribute');
3+
const { parseExpression } = require('../../parser');
4+
const adapter = require('../../adapter').ali;
5+
const genCode = require('../../codegen/genCode');
6+
7+
describe('Transform JSX Attribute', () => {
8+
it('should transform attribute name is key', () => {
9+
const code = '<View key={1}>test</View>';
10+
const ast = parseExpression(code);
11+
_transformAttribute(ast, code, adapter);
12+
expect(genCode(ast).code).toEqual('<View a:key={1}>test</View>');
13+
});
14+
it('should transform attribute name is className', () => {
15+
const code = '<View className="box">test</View>';
16+
const ast = parseExpression(code);
17+
_transformAttribute(ast, code, adapter);
18+
expect(genCode(ast).code).toEqual('<View class="box">test</View>');
19+
});
20+
it("should collect attribute name is ref and parse it's value as a string", () => {
21+
const code = '<View ref={scrollViewRef}>test</View>';
22+
const ast = parseExpression(code);
23+
const refs = _transformAttribute(ast, code, adapter);
24+
expect(genCode(ast).code).toEqual('<View ref="scrollViewRef">test</View>');
25+
expect(refs).toEqual([t.stringLiteral('scrollViewRef')]);
26+
});
27+
});

packages/jsx-compiler/src/modules/__tests__/condition.js

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ describe('Transform condition', () => {
3939
const dynamicValue = _transformTemplate(ast, adapter, {});
4040

4141
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>');
42-
expect(genDynamicValue(dynamicValue)).toEqual('{ foo: foo, bar: bar }');
42+
expect(genDynamicValue(dynamicValue)).toEqual('{ bar: bar, foo: foo }');
4343
});
4444

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

5151
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>');
52-
expect(genDynamicValue(dynamicValue)).toEqual('{ empty: empty, loading: loading }');
52+
expect(genDynamicValue(dynamicValue)).toEqual('{ loading: loading, empty: empty }');
5353
});
5454

5555
it('skip list dynamic value', () => {
@@ -88,6 +88,30 @@ describe('Transform condition', () => {
8888
</View>`);
8989
expect(genDynamicValue(dynamicValue)).toEqual('{ tabList: tabList }');
9090
});
91+
92+
it('transform simple logical expression', () => {
93+
const ast = parseExpression(`
94+
<View>
95+
{ a && <View>1</View>}
96+
</View>
97+
`);
98+
_transformTemplate(ast, adapter, {});
99+
expect(genCode(ast).code).toEqual(`<View>
100+
<block a:if="{{a}}"><View>1</View></block><block a:else>{a}</block>
101+
</View>`);
102+
});
103+
104+
it('transform nested logical expression', () => {
105+
const ast = parseExpression(`
106+
<View>
107+
{ a || b && <View>1</View>}
108+
</View>
109+
`);
110+
_transformTemplate(ast, adapter, {});
111+
expect(genCode(ast).code).toEqual(`<View>
112+
<block a:if={!a}><block a:if="{{b}}"><View>1</View></block><block a:else>{b}</block></block><block a:else>{a}</block>
113+
</View>`);
114+
});
91115
});
92116

93117
describe('Transiform condition render function', () => {

packages/jsx-compiler/src/modules/__tests__/element.js

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -109,15 +109,6 @@ describe('Transform JSXElement', () => {
109109
expect(genInlineCode(ast).code).toEqual('<View>{{ _d0 ? _d0.b[_d1.d] : 1 }}</View>');
110110
expect(genDynamicAttrs(dynamicValues)).toEqual('{ _d0: a, _d1: c }');
111111
});
112-
113-
it('should adapt attribute key', () => {
114-
const sourceCode = '<View key={\'key\'}>{ bar }</View>';
115-
const ast = parseExpression(sourceCode);
116-
const { dynamicValues } = _transform(ast, null, adapter, sourceCode);
117-
const code = genInlineCode(ast).code;
118-
expect(code).toEqual('<View a:key=\'key\'>{{ _d0 }}</View>');
119-
expect(genDynamicAttrs(dynamicValues)).toEqual('{ _d0: bar }');
120-
});
121112
});
122113

123114
describe('event handlers', () => {
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
const t = require('@babel/types');
2+
const traverse = require('../utils/traverseNodePath');
3+
const genExpression = require('../codegen/genExpression');
4+
const CodeError = require('../utils/CodeError');
5+
6+
function transformAttribute(ast, code, adapter) {
7+
const refs = [];
8+
traverse(ast, {
9+
JSXAttribute(path) {
10+
const { node } = path;
11+
const attrName = node.name.name;
12+
switch (attrName) {
13+
case 'key':
14+
node.name.name = adapter.key;
15+
break;
16+
case 'className':
17+
node.name.name = adapter.className;
18+
break;
19+
case 'ref':
20+
if (t.isJSXExpressionContainer(node.value)) {
21+
node.value = t.stringLiteral(genExpression(node.value.expression));
22+
}
23+
if (t.isStringLiteral(node.value)) {
24+
refs.push(node.value);
25+
} else {
26+
throw new CodeError(code, node, path.loc, "Ref's type must be string or jsxExpressionContainer");
27+
}
28+
break;
29+
default:
30+
path.stop();
31+
}
32+
}
33+
});
34+
return refs;
35+
}
36+
37+
module.exports = {
38+
parse(parsed, code, options) {
39+
parsed.refs = transformAttribute(parsed.templateAST, code, options.adapter);
40+
},
41+
42+
// For test cases.
43+
_transformAttribute: transformAttribute,
44+
};

packages/jsx-compiler/src/modules/code.js

Lines changed: 79 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,16 @@ const SAFE_CREATE_STYLE = '__create_style__';
2020
const USE_EFFECT = 'useEffect';
2121
const USE_STATE = 'useState';
2222
const USE_CONTEXT = 'useContext';
23-
const USE_HISTORY = 'useHistory';
24-
const USE_LOCATION = 'useLocation';
23+
const USE_REF = 'useRef';
2524

2625
const EXPORTED_DEF = '__def__';
27-
const RUNTIME = '/npm/jsx2mp-runtime';
26+
const RUNTIME = 'jsx2mp-runtime';
2827

28+
const getRuntimeByPlatform = (platform) => `${RUNTIME}/dist/jsx2mp-runtime.${platform}.esm`;
2929
const isAppRuntime = (mod) => mod === 'rax-app';
3030
const isFileModule = (mod) => /\.(png|jpe?g|gif|bmp|webp)$/.test(mod);
3131

32-
const isHooksAPI = (node) => [USE_EFFECT, USE_STATE, USE_CONTEXT,
33-
USE_HISTORY, USE_LOCATION].includes(node.name);
32+
const isCoreHooksAPI = (node) => [USE_EFFECT, USE_STATE, USE_CONTEXT, USE_REF].includes(node.name);
3433

3534
function getConstructor(type) {
3635
switch (type) {
@@ -50,13 +49,14 @@ function getConstructor(type) {
5049
module.exports = {
5150
parse(parsed, code, options) {
5251
const { defaultExportedPath, eventHandlers = [] } = parsed;
53-
if (options.type !== 'app' && (!defaultExportedPath || !defaultExportedPath.node)) {
52+
const { platform, type, cwd, outputPath, sourcePath, resourcePath, disableCopyNpm } = options;
53+
if (type !== 'app' && (!defaultExportedPath || !defaultExportedPath.node)) {
5454
// Can not found default export, otherwise app.js is excluded.
5555
return;
5656
}
5757
let userDefineType;
5858

59-
if (options.type === 'app') {
59+
if (type === 'app') {
6060
userDefineType = 'function';
6161
} else if (isFunctionComponent(defaultExportedPath)) { // replace with class def.
6262
userDefineType = 'function';
@@ -91,28 +91,30 @@ module.exports = {
9191
}
9292

9393
const hooks = collectHooks(parsed.renderFunctionPath);
94-
95-
const targetFileDir = dirname(join(options.outputPath, relative(options.sourcePath, options.resourcePath)));
94+
const targetFileDir = dirname(join(outputPath, relative(sourcePath, resourcePath)));
95+
const runtimePath = getRuntimePath(outputPath, targetFileDir, platform, disableCopyNpm);
9696

9797
removeRaxImports(parsed.ast);
98-
renameCoreModule(parsed.ast, options.outputPath, targetFileDir);
98+
renameCoreModule(parsed.ast, runtimePath);
9999
renameFileModule(parsed.ast);
100-
renameAppConfig(parsed.ast, options.sourcePath, options.resourcePath);
100+
renameAppConfig(parsed.ast, sourcePath, resourcePath);
101101

102-
const currentNodeModulePath = join(options.sourcePath, 'npm');
103-
const npmRelativePath = relative(dirname(options.resourcePath), currentNodeModulePath);
104-
renameNpmModules(parsed.ast, npmRelativePath, options.resourcePath, options.cwd);
102+
if (!disableCopyNpm) {
103+
const currentNodeModulePath = join(sourcePath, 'npm');
104+
const npmRelativePath = relative(dirname(resourcePath), currentNodeModulePath);
105+
renameNpmModules(parsed.ast, npmRelativePath, resourcePath, cwd);
106+
}
105107

106-
if (options.type !== 'app') {
107-
addDefine(parsed.ast, options.type, options.outputPath, targetFileDir, userDefineType, eventHandlers, parsed.useCreateStyle, hooks);
108+
if (type !== 'app') {
109+
addDefine(parsed.ast, type, userDefineType, eventHandlers, parsed.useCreateStyle, hooks, runtimePath);
108110
}
109111

110112
removeDefaultImports(parsed.ast);
111113

112114
/**
113115
* updateChildProps: collect props dependencies.
114116
*/
115-
if (options.type !== 'app' && parsed.renderFunctionPath) {
117+
if (type !== 'app' && parsed.renderFunctionPath) {
116118
const fnBody = parsed.renderFunctionPath.node.body.body;
117119
let firstReturnStatementIdx = -1;
118120
for (let i = 0, l = fnBody.length; i < l; i++) {
@@ -159,6 +161,7 @@ module.exports = {
159161
addUpdateData(parsed.dynamicValue, parsed.renderItemFunctions, parsed.renderFunctionPath);
160162
addUpdateEvent(parsed.dynamicEvents, parsed.eventHandler, parsed.renderFunctionPath);
161163
addProviderIniter(parsed.contextList, parsed.renderFunctionPath);
164+
addRegisterRefs(parsed.refs, parsed.renderFunctionPath);
162165
}
163166
},
164167
};
@@ -176,14 +179,21 @@ function genTagIdExp(expressions) {
176179
return parseExpression(ret);
177180
}
178181

179-
function renameCoreModule(ast, outputPath, targetFileDir) {
182+
function getRuntimePath(outputPath, targetFileDir, platform, disableCopyNpm) {
183+
let runtimePath = getRuntimeByPlatform(platform.type);
184+
if (!disableCopyNpm) {
185+
runtimePath = relative(targetFileDir, join(outputPath, 'npm', RUNTIME));
186+
runtimePath = runtimePath[0] !== '.' ? './' + runtimePath : runtimePath;
187+
}
188+
return runtimePath;
189+
}
190+
191+
function renameCoreModule(ast, runtimePath) {
180192
traverse(ast, {
181193
ImportDeclaration(path) {
182194
const source = path.get('source');
183195
if (source.isStringLiteral() && isAppRuntime(source.node.value)) {
184-
let runtimeRelativePath = relative(targetFileDir, join(outputPath, RUNTIME));
185-
runtimeRelativePath = runtimeRelativePath[0] !== '.' ? './' + runtimeRelativePath : runtimeRelativePath;
186-
source.replaceWith(t.stringLiteral(runtimeRelativePath));
196+
source.replaceWith(t.stringLiteral(runtimePath));
187197
}
188198
}
189199
});
@@ -282,7 +292,7 @@ function renameNpmModules(ast, npmRelativePath, filename, cwd) {
282292
});
283293
}
284294

285-
function addDefine(ast, type, outputPath, targetFileDir, userDefineType, eventHandlers, useCreateStyle, hooks) {
295+
function addDefine(ast, type, userDefineType, eventHandlers, useCreateStyle, hooks, runtimePath) {
286296
let safeCreateInstanceId;
287297
let importedIdentifier;
288298
switch (type) {
@@ -323,13 +333,10 @@ function addDefine(ast, type, outputPath, targetFileDir, userDefineType, eventHa
323333
));
324334
}
325335

326-
let runtimeRelativePath = relative(targetFileDir, join(outputPath, RUNTIME));
327-
runtimeRelativePath = runtimeRelativePath[0] !== '.' ? './' + runtimeRelativePath : runtimeRelativePath;
328-
329336
path.node.body.unshift(
330337
t.importDeclaration(
331338
specifiers,
332-
t.stringLiteral(runtimeRelativePath)
339+
t.stringLiteral(runtimePath)
333340
)
334341
);
335342

@@ -405,7 +412,7 @@ function collectHooks(root) {
405412
traverse(root, {
406413
CallExpression(path) {
407414
const { node } = path;
408-
if (t.isIdentifier(node.callee) && isHooksAPI(node.callee)) {
415+
if (t.isIdentifier(node.callee) && isCoreHooksAPI(node.callee)) {
409416
ret[node.callee.name] = true;
410417
}
411418
}
@@ -468,6 +475,51 @@ function addProviderIniter(contextList, renderFunctionPath) {
468475
}
469476
}
470477

478+
/**
479+
* Insert register ref method
480+
* @param {Array} refs
481+
* @param {Object} renderFunctionPath
482+
* */
483+
function addRegisterRefs(refs, renderFunctionPath) {
484+
const registerRefsMethods = t.memberExpression(
485+
t.thisExpression(),
486+
t.identifier('_registerRefs')
487+
);
488+
const fnBody = renderFunctionPath.node.body.body;
489+
/**
490+
* this._registerRefs([
491+
* {
492+
* name: 'scrollViewRef',
493+
* method: scrollViewRef
494+
* }
495+
* ])
496+
* */
497+
const scopedRefs = [];
498+
const stringRefs = [];
499+
refs.map(ref => {
500+
if (renderFunctionPath.scope.hasBinding(ref.value)) {
501+
scopedRefs.push(ref);
502+
} else {
503+
stringRefs.push(ref);
504+
}
505+
});
506+
if (scopedRefs.length > 0) {
507+
fnBody.push(t.expressionStatement(t.callExpression(registerRefsMethods, [
508+
t.arrayExpression(scopedRefs.map(ref => {
509+
return t.objectExpression([t.objectProperty(t.stringLiteral('name'), ref),
510+
t.objectProperty(t.stringLiteral('method'), t.identifier(ref.value))]);
511+
}))
512+
])));
513+
}
514+
if (stringRefs.length > 0) {
515+
fnBody.unshift(t.expressionStatement(t.callExpression(registerRefsMethods, [
516+
t.arrayExpression(stringRefs.map(ref => {
517+
return t.objectExpression([t.objectProperty(t.stringLiteral('name'), ref)]);
518+
}))
519+
])));
520+
}
521+
}
522+
471523
/**
472524
* For that alipay build folder can not contain `@`, escape to `_`.
473525
*/

0 commit comments

Comments
 (0)