看了會 snowpack 源碼

本文參照的 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-webpikapika-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.jsweb

再去找這個 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/webcli() 之間發生了什麼。

偏題了一會,咱們再來看看 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 呢)。相應的字段有 browsermain、和module。module 對應的固然就是 ESM 規範的入口文件。這纔是咱們的 snowpack 和 vite 所須要的。若是庫自己不支持輸出 ES modules 那隻能等社區或本身動手優化了。

最後,咱們知道 webpack 是web前端應用複雜化發展下的產物,將來是HTTP2 和 5G 普及的時代,webpack的初心會顯得多餘,可是就目前來看,它的成熟度帶來的優點是要蓋過給開發者帶來的負擔的。無論是snowpack 仍是 vite而言,相關庫對 ES modules 的支持度暫且不說,若是不能j繼續豐富插件生態,就還有很長的路要走,何況,說不許webpack下次升級就會支持類 snowpack 的打包方式呢。

相關文章
相關標籤/搜索