okam-build
Version:
The build tool for Okam develop framework
291 lines (263 loc) • 9.93 kB
JavaScript
/**
* @file Mini program transform visitor
* @author sparklewhy@gmail.com
*/
'use strict';
const {
createSimpleObjectExpression,
createImportDeclaration,
getPlainObjectNodeValue,
getBaseId,
getFrameworkExtendId,
removeNode,
removeComments
} = require('./helper');
const appTransformer = require('./app');
const componentTransformer = require('./component');
/**
* Traverse the module definition information and extract the `config` information
* for the app/page/component or mixins/components for page/component.
*
* @inner
* @param {Object} t the babel type definition
* @param {Object} initConfig the config used to cache the extracted information
* @param {Object} opts the transformation options
* @return {Object}
*/
function getCodeTraverseVisitors(t, initConfig, opts) {
let {isPage, isComponent, isBehavior, enableMixinSupport, filterOptions} = opts;
let hasComponents = isPage || isComponent;
return {
ObjectProperty(path) {
let prop = path.node;
let key = prop.key;
let keyName = key && key.name;
if (!isBehavior && keyName === 'config') {
// extract the app/component/page config definition
let config = getPlainObjectNodeValue(prop.value, path, t);
initConfig.config = config;
removeNode(t, path, {tail: true});
// skip children traverse
path.skip();
}
else if (enableMixinSupport && keyName === 'mixins') {
// extract the mixins information for page/component
let mixins = componentTransformer.getUsedMixinModulePaths(
prop.value, path, t, opts
);
initConfig.mixins = mixins;
path.skip();
}
else if (!isBehavior && hasComponents && keyName === 'components') {
// extract the using components information for page/component
let config = componentTransformer.getUsedComponentInfo(
prop.value, path, t
);
initConfig.components = config;
removeNode(t, path, {tail: true});
path.skip();
}
else if (filterOptions && !isBehavior && keyName === 'filters') {
const {getExportFilterNames, generateCode} = require('./filter');
let filterNames = getExportFilterNames(prop.value, t);
if (filterOptions.keepFiltersProp) {
initConfig.filters = {
filterNames
};
}
else {
let code = generateCode(prop.value, t, filterOptions);
initConfig.filters = {
code,
filterNames
};
removeNode(t, path, {tail: true});
}
path.skip();
}
else {
path.skip();
}
}
};
}
/**
* Create init call arguments
*
* @inner
* @param {Object} declarationPath the declaration statement path to process
* @param {Object} config the config property value info defined in module
* @param {Object} opts the transformation options
* @param {Object} t the babel type definition
* @return {Array}
*/
function createInitCallArgs(declarationPath, config, opts, t) {
let {isPage, isComponent, isApp, getInitOptions} = opts;
let callArgs = [declarationPath.node];
let initOptions;
if (opts.tplRefs) {
initOptions = {refs: opts.tplRefs};
}
let extraInitOpts = getInitOptions && getInitOptions(
config, {isPage, isComponent, isApp}
);
if (extraInitOpts) {
initOptions || (initOptions = {});
Object.assign(initOptions, extraInitOpts);
}
if (initOptions) {
// insert the `ref` information defined in template to the constructor
callArgs.push(
createSimpleObjectExpression(
initOptions, t
)
);
}
return callArgs;
}
/**
* Transform mini program code
*
* @inner
* @param {Object} t the babel type definition
* @param {Object} path the node path to transform
* @param {Object} declarationPath the declaration statement path to process
* @param {Object} config the config used to cache the config information
* defined in the code
* @param {Object} opts the transformation options
*/
function transformMiniProgram(t, path, declarationPath, config, opts) {
if (t.isObjectExpression(declarationPath)) {
// extract the config information defined in the code
declarationPath.traverse(
getCodeTraverseVisitors(t, config, opts)
);
}
else if (opts.isExtension) {
return;
}
else {
throw path.buildCodeFrameError('export require plain object');
}
let baseClassName = path.scope.generateUid(opts.baseName);
let rootPath = path.findParent(p => t.isProgram(p));
let bodyPath = rootPath.get('body.0');
// ensure the inserted import declaration is after the leading comment
removeComments(t, bodyPath, 'leadingComments');
// insert the base name import statement
bodyPath.insertBefore(
createImportDeclaration(baseClassName, opts.baseId, t)
);
if (opts.isApp) {
// insert the app extension use statements
appTransformer.extendAppFramework(
t, path, bodyPath, baseClassName, opts
);
}
let callArgs = createInitCallArgs(declarationPath, config.config, opts, t);
let needExport = opts.needExport || !opts.baseClass;
let toReplacePath = needExport
? path.get('declaration')
: path;
// wrap the export module using the base name
let callExpression = t.callExpression(
t.identifier(baseClassName),
callArgs
);
if (opts.isBehavior || !opts.baseClass) {
toReplacePath.replaceWith(t.expressionStatement(
callExpression
));
}
else {
toReplacePath.replaceWith(
t.expressionStatement(
t.callExpression(
t.identifier(opts.baseClass),
[
callExpression
]
)
)
);
}
// stop traverse to avoid the new created export default statement above
// that be revisited in this plugin `ExportDefaultDeclaration` visitor
// path.stop();
}
/**
* Get transform options
*
* @inner
* @param {Object} options the transformation type options
* @param {Object} state the transformation plugin state
* @return {Object}
*/
function getTransformOptions(options, state) {
let transformOpts = Object.assign({}, options, state.opts);
transformOpts.baseId = options.extensionName
? getFrameworkExtendId(transformOpts.appType, options.extensionName, true)
: getBaseId(transformOpts.appType, transformOpts.baseName);
return transformOpts;
}
/**
* Get mini program script transform visitor
*
* @param {Object} options the options to create visitor
* @param {string} options.baseName the base name to create the App/Component etc.
* @param {boolean=} options.isApp whether is the app transformation
* @param {boolean=} options.isPage whether is the page transformation
* @param {boolean=} options.isComponent whether is the component transformation
* @param {boolean=} options.isBehavior whether is the behavior transformation
* @param {boolean=} options.isExtension whether is the extension transform, e.g., Behavior
* @param {string=} options.extensionName the extension name
* @param {boolean=} options.needExport whether need to export the module definition
* @param {boolean=} options.noBaseClass whether has none base class to wrap the
* App/Page/Component definition, optional, by default false
* @return {Function}
*/
function getMiniProgramVisitor(options) {
return function ({types: t}) {
return {
visitor: {
/**
* Process `export default {}` statement
*
* @param {Object} path the node path
* @param {Object} state the plugin state
*/
ExportDefaultDeclaration(path, state) {
let transformOpts = getTransformOptions(options, state);
let config = {};
transformMiniProgram(
t, path, path.get('declaration'), config, transformOpts
);
transformOpts.config && (transformOpts.config(config));
},
/**
* Process `module.exports = {}` statement
*
* @param {Object} path the node path
* @param {Object} state the plugin state
*/
AssignmentExpression(path, state) {
let transformOpts = getTransformOptions(options, state);
let node = path.node;
let left = node.left;
if (t.isMemberExpression(left)) {
let obj = left.object.name;
let prop = left.property.name;
if (obj === 'module' && prop === 'exports') {
let config = {};
transformMiniProgram(
t, path, path.get('right'), config, transformOpts
);
transformOpts.config && (transformOpts.config(config));
}
}
}
}
};
};
}
module.exports = exports = getMiniProgramVisitor;