本文參照的 snowpack 是 v0.6.1 版,固然這個時候它的名字實際上是 @pika/web前端
snowpack 和 vite 同樣都是利用瀏覽器原生對 ES modules 的支持提供了一種新的前端打包構建方式。初看也許挺唬人的,實際上實現思路很簡單。0.61版的代碼一共只有 400 來行。所作的事情用一句話也能說清楚:從項目根目錄的 package.json 裏收集依賴,抽取 node_modules 中代碼利用 rollup 獨立打包到 web_modules 文件夾下。固然了,引用方式也是須要對應更改的。node
關鍵函數有三個:cli()
、install()
和 resolveWebDependency()
。首先是 cli() 函數,也是 @pika/web 的主函數,去除掉配置項的解析,它所作的事情實際上就是去項目根目錄的 package.json 裏收集依賴,對應的是const arrayOfDeps = webDependencies || Object.keys(pkgManifest.dependencies || {});
這一行,經過Object.keys 把依賴的庫名做爲數組元素生成的依賴數組再交給install 去處理,最後利用 chalk
這個庫將構建過程和打包結果輸出的好看一點。webpack
export async function cli(args: string[]) {
const {
help,
sourceMap,
babel = false,
optimize = false,
strict = false,
clean = false,
dest = 'web_modules',
remoteUrl = 'https://cdn.pika.dev',
remotePackage: remotePackages = [],
} = yargs(args);
const destLoc = path.resolve(cwd, dest);
if (help) {
printHelp();
process.exit(0);
}
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 hasBrowserlistConfig =
!!pkgManifest.browserslist ||
!!process.env.BROWSERSLIST ||
fs.existsSync(path.join(cwd, '.browserslistrc')) ||
fs.existsSync(path.join(cwd, 'browserslist'));
spinner.start();
const startTime = Date.now();
const result = await install(arrayOfDeps, {//注入
isCleanInstall: clean,
destLoc,
namedExports,
isExplicit: doesWhitelistExist,
isStrict: strict,
isBabel: babel || optimize,
isOptimized: optimize,
sourceMap,
remoteUrl,
hasBrowserlistConfig,
remotePackages: remotePackages.map(p => p.split(',')),
});
if (result) {
spinner.succeed(
chalk.bold(`@pika/web`) +
` installed: ` +
formatDetectionResults(!doesWhitelistExist) +
'. ' +
chalk.dim(`[${((Date.now() - startTime) / 1000).toFixed(2)}s]`),
);
}
if (spinnerHasError) {
// Set the exit code so that programmatic usage of the CLI knows that there were errors.
spinner.warn(chalk(`Finished with warnings.`));
process.exitCode = 1;
}
}
複製代碼
依照官網文檔,咱們只須要在根目錄下執行 npx @pika/web
這條命令,就會自動完成構建打包工做。那麼問題來了,何以如此?git
咱們知道,但模塊配置了 bin
定義的時候,就會在安裝時,自動軟鏈到 node_modules/.bin 下。而 node_modules/.bin 也會被 npm 添加到 PATH 環境變量中。執行 npx @pika/web
,會到node_modules/.bin
路徑和環境變量$PATH
裏查看命令是否存在。因而咱們能夠在 node-modules/.bin 下看到這樣幾個文件:pika-web
、pika
、pika-web.cmd
... 就是沒有 @pika/web
。不知道是否是 npm 自動作了轉換,我也沒分清楚這幾個文件各自的含義。可是憑着直覺,咱們盲猜是 pika-web
。打開這個文件,它是這樣的:github
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
"$basedir/node" "$basedir/../@pika/web/dist-node/index.bin.js" "$@"
ret=$?
else
node "$basedir/../@pika/web/dist-node/index.bin.js" "$@"
ret=$?
fi
exit $ret
複製代碼
好吧,這是個shell 腳本,執行了 node" $basedir/../@pika/web/dist-node/index.bin.js
web
再去找這個 index.bin.js
文件:shell
#!/usr/bin/env node
'use strict';
let hasBundled = true
try {
require.resolve('./index.bundled.js');
} catch(err) {
// We don't have/need this on legacy builds and dev builds
// If an error happens here, throw it, that means no Node.js distribution exists at all.
hasBundled = false;
}
const cli = !hasBundled ? require('../') : require('./index.bundled.js');
if (cli.autoRun) {
return;
}
const run = cli.run || cli.cli || cli.default;
run(process.argv).catch(function (error) {
console.error(error.stack || error.message || error);
process.exitCode = 1;
});
複製代碼
終於咱們看到它執行 cli 了。npm
從另一個角度咱們也能夠找到它:npx會根據packagejson裏定義的bin 找入口。json
對應到 @pika/web,入口是這樣的:數組
"bin": {
"pika-web": "dist-node/index.bin.js"
},
複製代碼
以上細節大可能是看上去合理的推測,確實沒拎清楚 從 npx @pika/web
到 cli()
之間發生了什麼。
偏題了一會,咱們再來看看 install
函數。算了太長了,不看了。確實也沒什麼好說的,install 裏比較重要的一步是 對 js類型文件執行 第三個重點函數 resolveWebDependency()
const cwd = process.cwd();
console.log(cwd);
function resolveWebDependency(dep: string): string {
const nodeModulesLoc = path.join(cwd, 'node_modules', dep);//獲取 node_moudules 地址
let dependencyStats: fs.Stats;
try {
dependencyStats = fs.statSync(nodeModulesLoc);//返回關於文件的信息
} catch (err) {
throw new Error(`"${dep}" not found in your node_modules directory. Did you run npm install?`);
}
if (dependencyStats.isFile()) {
return nodeModulesLoc;//是文件則直接返回文件地址
}
if (dependencyStats.isDirectory()) {//是個文件目錄
const dependencyManifestLoc = path.join(nodeModulesLoc, 'package.json');//進入package.json
const manifest = require(dependencyManifestLoc);
if (!manifest.module) {
throw new ErrorWithHint(
`dependency "${dep}" has no ES "module" entrypoint.`,
chalk.italic(
`Tip: Find modern, web-ready packages at ${chalk.underline( 'https://pikapkg.com/packages', )}`,
),
);
}
const resPath = path.join(nodeModulesLoc, manifest.module)
console.log(resPath)
return resPath;
}
throw new Error(
`Error loading "${dep}" at "${nodeModulesLoc}". (MODE=${dependencyStats.mode}) `,
);
}
複製代碼
咱們以rollup 爲例 resolveWebDependency("rollup");
看看執行效果.
代碼也很好理解,其中有個小細節卻是值得關注:manifest.module
。村上春樹式提問:當咱們 import 一個 npm 包的時候,咱們 在import 什麼?
package.json 中main 字段指定的路徑啦,大部分人如是說到。可實際上,npm 包分爲:只容許在客戶端使用的和瀏覽器/服務端均可以使用的(這種描述也不許確。時代在進步。node 之前還不支持 ES modules 呢)。相應的字段有 browser
、main
、和module
。module 對應的固然就是 ESM 規範的入口文件。這纔是咱們的 snowpack 和 vite 所須要的。若是庫自己不支持輸出 ES modules 那隻能等社區或本身動手優化了。
最後,咱們知道 webpack 是web前端應用複雜化發展下的產物,將來是HTTP2 和 5G 普及的時代,webpack的初心會顯得多餘,可是就目前來看,它的成熟度帶來的優點是要蓋過給開發者帶來的負擔的。無論是snowpack 仍是 vite而言,相關庫對 ES modules 的支持度暫且不說,若是不能j繼續豐富插件生態,就還有很長的路要走,何況,說不許webpack下次升級就會支持類 snowpack 的打包方式呢。