2019年,距离es6正式发布已通过去了4年多了,es6给咱们带来了许多新特性,包括全新的JavaScript模块系统(ESM),它能够直接在浏览器运行。但通常咱们开发项目,仍是要引入Browserify和Webpack等打包工具进行打包,诚然,这些打包工具能够给项目带来不少好处、好比混淆、压缩和转译代码等等。但与此同时,也带给项目极大的复杂性,各类各样的配置文件和插件等。不少时候,咱们不得不进行打包,由于npm的存在,咱们一般会引用不少注册在npm
上面的包,早期的npm
包大部分都是common.js(cjs)
风格的,这意味着浏览器不能直接运行,因此通常都要通过打包工具转换成浏览器支持的模块。javascript
npm
后面推出了module
入口的模块,即ESM风格的模块,到目前为止,已经超过70000个包提供了ESM的版本,已经基本知足咱们开发的须要了。因此为何咱们不直接跳过打包这个步骤,直接运行呢?事实上,本身写的ESM模块能够直接运行,但大部分npm
包都会有依赖,浏览器没办法从node_modules
导入这些依赖,因此咱们依然须要对代码进行打包,转译。html
基于上述缘由,@pika/web诞生了。@pika/web
不是构建工具,而是依赖安装工具,它能够帮助你构建npm包,而后直接在浏览器上运行,只须要一行代码:java
npx @pika/web
复制代码
@pika/web
使用起来十分简单,咱们首先须要在package.json
的dependencies
显式声明项目所依赖的包:node
// package.json
// ...
"dependencies": {
"test": "^1.0.0"
},
复制代码
而后在项目中引用这个包:git
// index.js
import { name } from 'test'
console.log(name)
复制代码
而后执行npx @pika/web
es6
web_modules
的文件夹,里面放着项目依赖的文件,最后一步就是将替换项目引用依赖的地址
// before
import { name } from 'test'
// after
import { name } from './web_modules/test.js'
复制代码
最后一步,由于咱们的代码须要在浏览器执行,因此须要嵌入到html
文件中:github
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
</body>
<script type="module" src="./index.js"></script>
</html>
复制代码
而后用浏览器打开文件,你会获得你想要的结果。web
可能有的同窗会有说:“个人项目引用的依赖不少,总不能一个个去替换地址吧,多麻烦啊!”,没问题,pika提供了一个babel
的插件帮助咱们替换模块引用。npm
@pika/web
的原理很简单,首先它会从package.json
的dependencies
种寻找项目依赖,收集好依赖就会逐个解析依赖,生成rollup
的配置文件,最后用rollup
将每个依赖打包成模块,放到web_modules
中。json
// ...
const pkgManifest = require(path.join(cwd, 'package.json'));
const {namedExports, webDependencies} = pkgManifest['@pika/web'] || {
namedExports: undefined,
webDependencies: undefined,
};
const doesWhitelistExist = !!webDependencies;
const arrayOfDeps = webDependencies || Object.keys(pkgManifest.dependencies || {});
// ...
复制代码
// ...
const depManifestLoc = resolveFrom(cwd, `${dep}/package.json`);
const depManifest = require(depManifestLoc);
let foundEntrypoint: string = depManifest.module;
// If the package was a part of the explicit whitelist, fallback to it's main CJS entrypoint.
if (!foundEntrypoint && isExplicit) {
foundEntrypoint = depManifest.main || 'index.js';
}
// ...
复制代码
rollup
打包配置// ...
const inputOptions = {
input: depObject,
plugins: [
rollupPluginNodeResolve({
mainFields: ['browser', 'module', !isStrict && 'main'].filter(Boolean),
modulesOnly: isStrict, // Default: false
extensions: ['.mjs', '.cjs', '.js', '.json'], // Default: [ '.mjs', '.js', '.json', '.node' ]
// whether to prefer built-in modules (e.g. `fs`, `path`) or local ones with the same names
preferBuiltins: false, // Default: true
}),
!isStrict &&
rollupPluginCommonjs({
extensions: ['.js', '.cjs'], // Default: [ '.js' ]
namedExports: knownNamedExports,
}),
!!isBabel &&
rollupPluginBabel({
compact: false,
babelrc: false,
presets: [
[
babelPresetEnv,
{
modules: false,
targets: hasBrowserlistConfig ? undefined : '>0.75%, not ie 11, not op_mini all',
},
],
],
}),
!!isOptimized && rollupPluginTerser(),
],
}) as any,
};
const outputOptions = {
dir: destLoc,
format: 'esm' as 'esm',
sourcemap: sourceMap === undefined ? isOptimized : sourceMap,
exports: 'named' as 'named',
chunkFileNames: 'common/[name]-[hash].js',
};
const packageBundle = await rollup.rollup(inputOptions);
await packageBundle.write(outputOptions);
fs.writeFileSync(
path.join(destLoc, 'import-map.json'),
JSON.stringify({imports: importMap}, undefined, 2),
{encoding: 'utf8'},
);
// ...
复制代码