背景
项目长期迭代中,功能模块越来越多,项目越来越大,每次启动项目都需要等待很长时间,开发环境下热更新也特别慢。
并且很多时候,其实有些功能模块对于当前的需求是不必要的,有没有方法可以按需加载功能模块呢?
问:入口文件注释掉不需要的功能模块,不就好了?
答:这样必然可以,但是有没有更高级,更方便的方法呢。那有请今天的主角 jscodeshift。
jscodeshift
jscodeshift 是一个基于 codemod 理念的 JavaScript/TypeScript 重构工具,其原理是将 JS/TS 代码解析为抽象语法树(Abstract Syntax Tree,AST),并提供一系列用于访问和修改 AST 的 API 以实现自动化的代码重构。
使用 jscodeshift 可以进行一系列代码转换操作,比如替换变量名、修改函数调用、重构类定义等。它可以帮助开发人员快速而准确地进行大规模的代码修改,尤其适用于需要对遗留代码进行更新或者升级的情况。
方案
原始入口文件 App.tsx
import React from 'react';
import AComp from './aComp';
import BComp from './bComp';
import CComp from './cComp';
function App() {
return (
<div className="App">
<AComp />
<BComp />
<CComp />
</div>
);
}
export default App;
ondemand_build.js 生成新入口 NewApp.tsx
// ondemand_build.js
const { run: jscodeshift } = require('jscodeshift/src/Runner');
const path = require('path');
const colors = require('colors');
const options = {
dry: true,
print: false,
verbose: 1,
cpus: 1,
};
const transformPath = path.resolve(__dirname, './transformer.js');
const filepaths = [path.resolve(__dirname, './App.tsx')];
const runParse = async() => {
if (filepaths.length === 0) {
return;
}
try {
const res = await jscodeshift(transformPath, filepaths, options);
console.log(colors.bgRed('transform success!', res));
} catch (err) {
console.log(colors.bgYellow(err), '---err');
}
};
runParse();
transformer.js
const fs = require('fs');
const path = require('path');
const outputFilePath = path.resolve(__dirname, './NewApp.tsx');
module.exports = function transformer(file, api, options) {
const j = api.jscodeshift;
const ast = j(file.source);
ast.find(j.ImportDeclaration).forEach(path => {
let needChange = false;
let name = '';
j(path).find(j.Identifier).forEach(path1 => {
if (path1.value.name === 'AComp' || path1.value.name === 'BComp') {
// 将 AComp 和 BComp 删除
needChange = true;
name = path1.value.name;
}
});
if (needChange) {
j(path).replaceWith();
j(path).insertBefore(`const ${name} = () => null;`);
}
});
fs.writeFileSync(outputFilePath, ast.toSource(), 'utf-8');
return ast.toSource();
};
module.exports.parser = 'tsx';
新入口 NewApp.tsx
import React from 'react';
import CComp from './cComp.tsx';
const AComp = () => null;
const BComp = () => null;
function App() {
return (
<div className="App">
<AComp />
<BComp />
<CComp />
</div>
);
}
export default App;
总结
如果想要更加的智能,可以利用 inquirer 命令行交互工具,在 yarn start 动态选择需要加载的功能模块,收集用户答案后,以键值对的方式存储到配置文件中,transformer.js 根据配置文件动态按需引入需要的功能模块。