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'},
);
// ...
複製代碼