前言:
现在小伙伴们对“jquerytree异步加载”大致比较注重,小伙伴们都想要了解一些“jquerytree异步加载”的相关资讯。那么小编同时在网上汇集了一些关于“jquerytree异步加载””的相关资讯,希望我们能喜欢,你们一起来了解一下吧!一、介绍
umijs/father 是个由 lerna 管理的,基于 rollup 和 babel 的工具库。组件打包功能主要是 packages 下的 father-build 实现的。如果只做组件打包,不需要文档功能,可直接安装 father-build,使用和配置同 father。
1.1 father 特性
✔︎ 基于 docz 的文档功能(不再维护,建议 迁移到 dumi 或 单独安装 docz 使用)
✔︎ 基于 rollup 和 babel 的组件打包功能
✔︎ 支持 TypeScript
✔︎ 支持 cjs、esm 和 umd 三种格式的打包
✔︎ esm 支持生成 mjs,直接为浏览器使用
✔︎ 支持用 babel 或 rollup 打包 cjs 和 esm
✔︎ 支持多 entry
✔︎ 支持 lerna
✔︎ 支持 css 和 less,支持开启 css modules
✔︎ 支持 test
✔︎ 支持用 prettier 和 eslint 做 pre-commit 检查
1.2 CJS、ESM 和 UMD它们是什么?
它们是在 JS 里用来实现“模块”的不同规则。
CJS
CJS 就是 CommonJS 规范的缩写。
语法:
// doSomething.js// 导出module.exports = function doSomething(n) { // 做点啥}// 引入 const doSomething = require('./doSomething.js');
特点:
每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。每个模块内部,module 变量代表当前模块。这个变量是一个对象,它的 exports 属性(即module.exports)是对外的接口。 加载某个模块,其实是加载该模块的module.exports属性。模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。模块加载的顺序,按照其在代码中出现的顺序。值得一提的是,CommonJS 规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。UMD
UMD 是 Universal Module Definition(通用模块定义) 的缩写。
语法:
(function (root, factory) { if (typeof define === "function" && define.amd) { // AMD define(["jquery", "underscore"], factory); } else if (typeof exports === "object") { // CommonJS 等 module.exports = factory(require("jquery"), require("underscore")); } else { // 浏览器全局变量(root 即 window) root.Requester = factory(root.$, root._); }}(this, function ($, _) { // 方法 function a() {}; // 私有方法,因为它没有被返回(见下面) function b() {}; // 公共方法,因为被返回了 function c() {}; // 公共方法,因为被反会了 // 暴露公共方法 return { b: b, c: c }}));
特点:
UMD 是为了让模块同时兼容 AMD 和 CommonJS 规范而出现的,多被一些需要同时支持浏览器端和服务端引用的第三方库所使用,UMD是一个时代的产物。可以使用<script>标签直接引用。通常在 ESM 不起作用的情况下用作备用 。ESM
ESM 就是 ECMAScript Module 的缩写。
语法:
// 导出export default function() { // 做点啥};export const foo() {...};export const bar() {...};// 引入import {foo, bar} from './myLib';
特点:
ESM规范是ES标准的模块化规范它兼具两方面的优点:具有 CJS 的简单语法和 AMD 的异步CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。运行时加载: CommonJS 模块就是对象;即在输入时是先加载整个模块,生成一个对象,然后再从这个对象上面读取方法,这种加载称为“运行时加载”。编译时加载: ES6 模块不是对象,而是通过 export 命令显式指定输出的代码,import 时采用静态命令的形式。即在 import 时可以指定加载某个输出值,而不是加载整个模块,这种加载称为“编译时加载”。
模块规范
CJS
(CommonJS )
UMD
(Universal Module Definition)
ESM
(ECMAScript Module)
特点
同步加载不支持 tree-shaking可以使用<script>标签引用异步加载支持 tree-shaking
运行环境
仅 NodeJS
NodeJS、浏览器
NodeJS、浏览器
father-build 打包目录
lib 目录
dist 目录
es 目录
相关配置package.json 发行打包相关参数main:定义一个入口文件module:定义一个针对 es6 模块及语法的入口文件unpkg:unpkg 是一个前端常用的公共CDN 服务。配置了这个参数,可以让上传到 npm的所有文件都开启 unpkg 的 cdn 服务。webpack 的 target 属性
含义:由于 JavaScript 既可以编写服务端代码也可以编写浏览器代码,所以 webpack 提供了 target 属性,用来制定构建目标。
默认值:当配置了 browserslist 的时候,默认值是 "browserslist" ;否则就是 "web" 。
webpack.config.js
module.exports = { target: 'node',};
webpack 的 resolve.mainFields 属性
含义:当从 npm 包中导入模块时(例如,import * as D3 from 'd3'),此选项将决定在 npm 包的 package.json 中使用哪个字段导入模块。
默认值:根据 webpack 配置中指定的 target 不同,默认值也会有所不同。
当 target 属性设置为 webworker, web 或者没有指定的话:
webpack.config.js
module.exports = { //... resolve: { mainFields: ['browser', 'module', 'main'], },};
对于其他任意的 target(包括 node),默认值为:
webpack.config.js
module.exports = { //... resolve: { mainFields: ['module', 'main'], },};
项目在解析依赖包的文件时,是按照 mainFields 中属性的顺序决定优先级。
比如对于以上 antd 的 package.json 中的配置,如果我们项目中 target 没有指定,那默认会先找 browser 配置的入口文件,没有的话,再找 module 属性配置的文件。
1.3 常用配置
以下是 公司项目的 father 配置:
import {readdirSync} from 'fs';import {join} from 'path';const headPkgs: string[] = [ 'emotion', ...];const tailPkgs = [];const type = process.env.BUILD_TYPE;let config = {};if (type === 'es') { config = { // 是否输出 cjs 格式,以及指定 cjs 格式的打包方式等。 cjs: false, // 是否输出 esm 格式,以及指定 esm 格式的打包方式等。 esm: { // 指定 esm 的打包类型,可选 rollup 或 babel。 type: 'rollup', // 是否在 esm 模式下把 import 项里的 /lib/ 转换为 /es/。 // 比如 import 'foo/lib/button';,在 cjs 模式下会保持原样,在 esm 模式下会编译成 import 'foo/es/button';。 importLibToEs: true, }, // 是否把 helper 方法提取到 @babel/runtime 里。 // 推荐开启,能节约不少尺寸 // runtimeHelpers 只对 esm 有效,cjs 下无效,因为 cjs 已经不给浏览器用了,只在 ssr 时会用到,无需关心小的尺寸差异 runtimeHelpers: true, // 自定义 packages 目录下的构建顺序 pkgs: [...headPkgs, ...tailPkgs], // 配置是否开启 css modules。 // 如果组件中用了 css modules,但不开启这个配置,会导致样式引入失败 // 虽然 father 提供这个能力,但不建议为组件库启用 CSS Modules, // 这将使得组件库用户很难覆写样式,下一版的 father 也将移除该特性。 cssModules: true, // 在 rollup 模式下做 less 编译,支持配置 less 在编译过程中的 Options lessInRollupMode: { javascriptEnabled: true, }, // 配置额外的 babel plugin extraBabelPlugins: [ ['babel-plugin-import', { libraryName: 'antd', libraryDirectory: 'es', style: true }, 'antd'], ], // 是否禁用类型检测。 disableTypeCheck: true };}export default config;二、调试
那 father-build 是如何工作的呢?
先来建一个项目(umi 搭建一个) 。使用 vscode 来调试,配置 launch.json
{ "version": "0.2.0", "configurations": [ { "type": "node", "request": "launch", "name": "Debug father", "skipFiles": [ "<node_internals>/**" ], "runtimeExecutable": "npm", "runtimeArgs": [ "run-script", "build" ] } ]}点击 debug,开始调试三、源码分析
在前面的调试中,我们可以看到,入口文件是 bin/father-build.js,在 father-build 中,支持通过命令行传递参数。相应的源码如下:
const args = yParser(process.argv.slice(2)); const buildArgs = stripEmptyKeys({ esm: args.esm && { type: args.esm === true ? 'rollup' : args.esm }, cjs: args.cjs && { type: args.cjs === true ? 'rollup' : args.cjs }, umd: args.umd && { name: args.umd === true ? undefined : args.umd }, file: args.file, target: args.target, entry: args._, config: args.config, });
比如 node ./bin/father-build.js --esm --cjs --umd --file bar ./src/index.js ,然后通过 yargs-parser 进行解析,得到结果为
然后进入到 build.ts文件,判断是否使用了 lerna,然后根据是否是 lerna来调整打包的逻辑。代码如下:
const useLerna = existsSync(join(opts.cwd, 'lerna.json')); const isLerna = useLerna && process.env.LERNA !== 'none'; const dispose = isLerna ? await buildForLerna(opts) : await build(opts);先来看一下非 lerna 模式的打包逻辑:首先使用了 babel-register。使用 babel-register 之后,后续被 node 使用 require 语法引用的文件,都会被 babel 进行代码转换。获取打包配置 getBundleOpts(opts) 。根据配置的 cjs、umd、esm 选项,来开始使用 babel 或者 rollup 进行打包。
代码逻辑如下:
export async function build(opts: IOpts, extraOpts: IExtraBuildOpts = {}) { ... // register babel for config files registerBabel({ cwd, only: customConfigPath ? CONFIG_FILES.concat(customConfigPath) : CONFIG_FILES, }); // Get user config const bundleOptsArray = getBundleOpts(opts); for (const bundleOpts of bundleOptsArray) { ... // Build umd if (bundleOpts.umd) { log(`Build umd`); await rollup({ cwd, rootPath, log, type: 'umd', entry: bundleOpts.entry, watch, dispose, bundleOpts, }); } // Build cjs if (bundleOpts.cjs) { const cjs = bundleOpts.cjs as IBundleTypeOutput; log(`Build cjs with ${cjs.type}`); if (cjs.type === 'babel') { await babel({ cwd, rootPath, watch, dispose, type: 'cjs', log, bundleOpts }); } else { await rollup({ cwd, rootPath, log, type: 'cjs', entry: bundleOpts.entry, watch, dispose, bundleOpts, }); } } // Build esm if (bundleOpts.esm) { const esm = bundleOpts.esm as IEsm; log(`Build esm with ${esm.type}`); const importLibToEs = esm && esm.importLibToEs; if (esm && esm.type === 'babel') { await babel({ cwd, rootPath, watch, dispose, type: 'esm', importLibToEs, log, bundleOpts }); } else { await rollup({ cwd, rootPath, log, type: 'esm', entry: bundleOpts.entry, importLibToEs, watch, dispose, bundleOpts, }); } } } return dispose;}接下来看一下 father-build 内部是怎么获取用户配置的首先通过getExistFile() 来获取入口文件,内部是 fs 模块的 existsSync 方法判断入口文件是否存在,通常就是我们项目下 src/index.js。接着通过 getUserConfig() 来获取用户的配置信息,内部也是通过 fs 模块判断 .fatherrc.js, .fatherrc.jsx, fatherrc.ts', .fatherrc.tsx, .umirc.library.js, .umirc.library.jsx, umirc.library.ts, umirc.library.tsx 是否存在,存在的话,就读取里面的配置信息 ,通常就是我们项目下的配置的.fatherrc.js文件配置的打包参数。同时通过 ajv这个包,来对 schema.ts 中定义配置文件应该遵循的格式进行校验。根据获取的 userConfig 开始启用 babel 模式或者 rollup 模式打包。
接下来,看看获取到配置信息之后,father-build 是如何使用 babel 或者 rollup 打包的。
先来看一下 babel 的实现。
代码中硬编码了读取 src 目录,因此此时的 entry 配置是无效的。然后通过 pattern 找出需要编译的文件,进入到 createStream 方法。
核心代码如下:
function createStream(src) { const tsConfig = getTSConfig(); const babelTransformRegexp = disableTypeCheck ? /\.(t|j)sx?$/ : /\.jsx?$/; function isTsFile(path) { return /\.tsx?$/.test(path) && !path.endsWith(".d.ts"); } function isTransform(path) { return babelTransformRegexp.test(path) && !path.endsWith(".d.ts"); } return vfs // 读取源文件 .src(src, { allowEmpty: true, base: srcPath, }) // gulp-plumber这是一款防止因 gulp 插件的错误而导致管道中断,plumber 可以阻止 gulp 插件发生错误导致进程退出并输出错误日志。 .pipe(watch ? gulpPlumber() : through.obj()) .pipe( // 先处理 ts gulpIf((f) => !disableTypeCheck && isTsFile(f.path), gulpTs(tsConfig)) ) .pipe( gulpIf( // 处理 less 文件 (f) => lessInBabelMode && /\.less$/.test(f.path), gulpLess(lessInBabelMode || {}) ) ) .pipe( gulpIf( (f) => isTransform(f.path), through.obj((file, env, cb) => { try { file.contents = Buffer.from( // 遇到 tsx, jsx 就用 babel 去处理 // transform 方法也就是根据 babel 配置来编译文件 transform({ file, type, }) ); // .jsx -> .js file.path = file.path.replace(extname(file.path), ".js"); cb(null, file); } catch (e) { signale.error(`Compiled faild: ${file.path}`); console.log(e); cb(null); } }) ) ) // const srcPath = join(cwd, "src"); // const targetDir = type === "esm" ? "es" : "lib"; // const targetPath = join(cwd, targetDir); .pipe(vfs.dest(targetPath)); }再来看一下 rollup的实现。
如果选择使用 rollup 进行打包,那么代码就会先经过 rollup.ts 进入到 getRollupConfig.ts 中来,且在进入到 getRollupConfig 之前,会经过 normalizeBundleOpts 处理一些入参,比如处理 overridesByEntry 参数。到了 getRollupConfig.ts 中,就根据 type 来拼装 rollup 的参数, 包括组合 plugins,externals 来进行编译。
核心代码如下:
switch (type) { case 'esm': const output: Record<string, any> = { dir: join(cwd, `${esm && (esm as any).dir || 'dist'}`), entryFileNames: `${(esm && (esm as any).file) || `${name}.esm`}.js`, } return [ { input, output: { format, ...output, }, plugins: [...getPlugins(), ...(esm && (esm as any).minify ? [terser(terserOpts)] : [])], external: testExternal.bind(null, external, externalsExclude), }, ...(esm && (esm as any).mjs ? [ { input, output: { format, file: join(cwd, `dist/${(esm && (esm as any).file) || `${name}`}.mjs`), }, plugins: [ ...getPlugins(), replace({ 'process.env.NODE_ENV': JSON.stringify('production'), }), terser(terserOpts), ], external: testExternal.bind(null, externalPeerDeps, externalsExclude), }, ] : []), ]; case 'cjs': return [ { input, output: { format, file: join(cwd, `dist/${(cjs && (cjs as any).file) || name}.js`), }, plugins: [...getPlugins(), ...(cjs && (cjs as any).minify ? [terser(terserOpts)] : [])], external: testExternal.bind(null, external, externalsExclude), }, ]; case 'umd': // Add umd related plugins const extraUmdPlugins = [ commonjs({ include, // namedExports options has been remove from }), ]; return [ { input, output: { format, sourcemap: umd && umd.sourcemap, file: join(cwd, `dist/${(umd && umd.file) || `${name}.umd`}.js`), globals: umd && umd.globals, name: (umd && umd.name) || (pkg.name && camelCase(basename(pkg.name))), }, plugins: [ ...extraUmdPlugins, ...getPlugins(), replace({ 'process.env.NODE_ENV': JSON.stringify('development'), }), ], external: testExternal.bind(null, externalPeerDeps, externalsExclude), }, ...(umd && umd.minFile === false ? [] : [ { input, output: { format, sourcemap: umd && umd.sourcemap, file: join(cwd, `dist/${(umd && umd.file) || `${name}.umd`}.min.js`), globals: umd && umd.globals, name: (umd && umd.name) || (pkg.name && camelCase(basename(pkg.name))), }, plugins: [ ...extraUmdPlugins, ...getPlugins({ minCSS: true }), replace({ 'process.env.NODE_ENV': JSON.stringify('production'), }), terser(terserOpts), ], external: testExternal.bind(null, externalPeerDeps, externalsExclude), }, ]), ]; default: throw new Error(`Unsupported type ${type}`); }项目踩坑babel 模式默认不支持 cssModules。babel 模式打包资源放到 dist、es、lib 文件夹下,rollup 模式全部放到 dist 文件夹下(新的 father-build 的 esm 已经支持定义 dir,将打包资源放到自定义文件夹下,但是esm.mjs格式的资源还不能自定义,还是打包到 dist 文件夹下面 )。boss 中 webpack 配置添加 module,因为 father-build 不同模式、打包的资源放到的文件夹不一样,要注意发包的时候 package.json 的资源引用配置,可能因为 webpack 配置resolve 添加的 module 导致资源找不到。五、参考链接
GitHub - umijs/father: Library toolkit based on rollup and babel.
标签: #jquerytree异步加载