最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

rollup?cli開發(fā)全面系統(tǒng)性rollup源碼分析

 更新時間:2023年01月30日 13:59:43   作者:Victor  
這篇文章主要為大家介紹了rollup?cli開發(fā)全網(wǎng)系統(tǒng)性rollup源碼分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

引言

在學(xué)習(xí) rollup CLI 之前我們需要了解 npm 中的 prefix,symlink,Executables 這三個概念。

prefix

當(dāng)我們使用 --global/-g 選項(xiàng)的時候會將包安裝到 prefix 目錄下,prefix 默認(rèn)為 node 的安裝位置。在大多數(shù)系統(tǒng)上,它是 /usr/local。

在 Windows 上,它是 %AppData%\npm 目錄中。在 Unix 系統(tǒng)上,它向上一級,因?yàn)?node 通常安裝在{prefix}/bin/node 而不是{prefix}/node.exe。

如果未使用 --global/-g 選項(xiàng)的時候,它將安裝在當(dāng)前包的根目錄,或者當(dāng)前工作目錄。

具體請參閱 folders

symlink

許多包都有一個或多個可執(zhí)行文件,并且希望將其安裝到 PATH 中。npm 剛好提供了這個功能。

如果想要在用戶安裝包的時候創(chuàng)建可執(zhí)行文件,請?jiān)?package.json 中提供一個 bin 字段,該字段是命令名稱到本地文件名的映射。在安裝時,npm 會將該文件符號鏈接到 prefix/bin 以進(jìn)行全局安裝,或 ./node_modules/.bin/ 用于本地安裝。

Executables(可執(zhí)行文件)

舉個例子:

npm install --global rollup

當(dāng)我們使用上述方式全局安裝 rollup 的時候,我們可以 cd 到任何文件目錄下直接使用 rollup 命令來使用它。其中的原理就是我們需要了解的可執(zhí)行文件的概念:

  • 在全局模式下,可執(zhí)行文件在 Unix 上鏈接到 {prefix}/bin,或在 Windows 上直接鏈接到 {prefix}。
  • 在本地模式下,可執(zhí)行文件鏈接到 ./node_modules/.bin 中,以便它們可以可用于通過 npm 運(yùn)行的腳本。

簡單來說就是當(dāng)你使用 npm install 的時候 npm 會自動為你創(chuàng)建對應(yīng)的可執(zhí)行文件。如果是使用 npm install 的方式則會將對應(yīng)的可執(zhí)行文件放在 /node_modules/.bin 目錄下。如果使用 npm install --global 的方式,對應(yīng)的可執(zhí)行文件在 Unix 上會放在{prefix}/bin 目錄,在 Windows 上則是 {prefix} 目錄。

當(dāng)你執(zhí)行 npm run 的時候,npm 會在 node 環(huán)境變量(Path)中(例如 C:\Users\victorjiang\AppData\Roaming\npm)找到對應(yīng)的 node 可執(zhí)行文件并且運(yùn)行它。可執(zhí)行文件包括三個:

  • rollup:Unix 系統(tǒng)默認(rèn)的可執(zhí)行文件,必須輸入完整文件名
  • rollup.cmd:windows cmd 中默認(rèn)的可執(zhí)行文件
  • rollup.ps1:Windows PowerShell 中可執(zhí)行文件,可以跨平臺

在了解了 prefix,symlink,Executables 這三個概念之后我們就可以開始學(xué)習(xí) rollup 的 CLI 的功能了。

rollup 命令行的開發(fā)

Rollup 命令行的源碼在項(xiàng)目的根目錄的 cli 下:

cli
├─ run              //定義了runRollup函數(shù),以及加載配置文件等業(yè)務(wù)代碼
├─ cli.ts           //命令行解析入口
├─ help.md          //rollup幫助文檔
├─ logging.ts       //handleError方法定義

cli/cli.ts 代碼定義:

import process from 'node:process';
import help from 'help.md';
import { version } from 'package.json';
import argParser from 'yargs-parser';
import { commandAliases } from '../src/utils/options/mergeOptions';
import run from './run/index';
/**
commandAliases: {
	c: 'config',
	d: 'dir',
	e: 'external',
	f: 'format',
	g: 'globals',
	h: 'help',
	i: 'input',
	m: 'sourcemap',
	n: 'name',
	o: 'file',
	p: 'plugin',
	v: 'version',
	w: 'watch'
};
 */
// process 是一個全局變量,即 global 對象的屬性。
// 它用于描述當(dāng)前Node.js 進(jìn)程狀態(tài)的對象,提供了一個與操作系統(tǒng)的簡單接口。
// process.argv 屬性返回一個數(shù)組,由命令行執(zhí)行腳本時的各個參數(shù)組成。
// 它的第一個成員總是node,第二個成員是腳本文件名,其余成員是腳本文件的參數(shù)。
// process.argv:  [
//   'C:\\Program Files\\nodejs\\node.exe',
//   'C:\\Program Files\\nodejs\\node_modules\\rollup\\dist\\bin\\rollup'
// ]
/**
 * 1. process.argv.slice(2) 則是從 argv數(shù)組下標(biāo)為2的元素開始直到末尾提取元素,舉例來說就是提取諸如 rollup -h 中除了 rollup 之外的參數(shù)
 * 2. yargs-parser這個包的作用是把命令行參數(shù)轉(zhuǎn)換為json對象,方便訪問。
 * 例如:"rollup -h" 會被argParser解析成 { _: [], h: true, help: true }
 * "rollup --help" 會被argParser解析成 { _: [], help: true, h: true }
 * 'camel-case-expansion' 表示連字符參數(shù)是否應(yīng)該擴(kuò)展為駝峰大小寫別名?默認(rèn)是true.
 * 例如: node example.js --foo-bar 會被解析成 { _: [], 'foo-bar': true, fooBar: true }
 *
 */
const command = argParser(process.argv.slice(2), {
  alias: commandAliases, //alias參數(shù)表示鍵的別名對象
  configuration: { 'camel-case-expansion': false } //為 argParser 解析器提供配置選項(xiàng), 'camel-case-expansion': false 表示連字符參數(shù)不會被擴(kuò)展為駝峰大小寫別名
});
//process.stdin.isTTY 用于檢測我們的程序是否直接連到終端
if (command.help || (process.argv.length <= 2 && process.stdin.isTTY)) {
  console.log(`\n${help.replace('__VERSION__', version)}\n`);
} else if (command.version) {
  console.log(`rollup v${version}`);
} else {
  try {
    // eslint-disable-next-line unicorn/prefer-module
    //瀏覽器是支持source maps的,但node環(huán)境原生不支持source maps。所以我們可以通過'source-map-support'包來實(shí)現(xiàn)這個功能。這樣當(dāng)程序執(zhí)行出錯的時候方便通過控制臺定位到源碼位置。
    require('source-map-support').install();
  } catch {
    // do nothing
  }
  run(command);
}

上面代碼中的 run 方法就是 cli/run/index.ts 中定義的 runRollup 方法,它的主要作用就是為了解析用戶輸入的命令行參數(shù)。

cli/run/index.ts 代碼定義:

import { env } from 'node:process';
import type { MergedRollupOptions } from '../../src/rollup/types';
import { errorDuplicateImportOptions, errorFailAfterWarnings } from '../../src/utils/error';
import { isWatchEnabled } from '../../src/utils/options/mergeOptions';
import { getAliasName } from '../../src/utils/relativeId';
import { loadFsEvents } from '../../src/watch/fsevents-importer';
import { handleError } from '../logging';
import type { BatchWarnings } from './batchWarnings';
import build from './build';
import { getConfigPath } from './getConfigPath';
import { loadConfigFile } from './loadConfigFile';
import loadConfigFromCommand from './loadConfigFromCommand';
export default async function runRollup(command: Record<string, any>): Promise<void> {
  let inputSource; //獲取input的值
  if (command._.length > 0) {
    //獲取非選項(xiàng)值
    //例如終端輸入"rollup -i input.js f es"  =>  command:  { _: [ 'f', 'es' ], i: 'input.js', input: 'input.js' }
    if (command.input) {
      handleError(errorDuplicateImportOptions());
    }
    inputSource = command._;
  } else if (typeof command.input === 'string') {
    inputSource = [command.input];
  } else {
    inputSource = command.input;
  }
  if (inputSource && inputSource.length > 0) {
    if (inputSource.some((input: string) => input.includes('='))) {
      //"rollup -i input.js f=es" => { _: [ 'f=es' ], i: 'input.js', input: 'input.js' }
      command.input = {};
      //處理多入口文件的情況
      for (const input of inputSource) {
        const equalsIndex = input.indexOf('=');
        const value = input.slice(Math.max(0, equalsIndex + 1)); //獲取等號右邊的字符=> “es”
        const key = input.slice(0, Math.max(0, equalsIndex)) || getAliasName(input); //獲取等號左邊的字符=> “f”
        command.input[key] = value;
      }
    } else {
      //處理單入口文件的情況
      command.input = inputSource;
    }
  }
  if (command.environment) {
    //獲取environment參數(shù)用于設(shè)置process.env.[XX]
    const environment = Array.isArray(command.environment)
      ? command.environment
      : [command.environment];
    for (const argument of environment) {
      for (const pair of argument.split(',')) {
        const [key, ...value] = pair.split(':');
        env[key] = value.length === 0 ? String(true) : value.join(':');
      }
    }
  }
  if (isWatchEnabled(command.watch)) {
    //觀察模式
    await loadFsEvents();
    const { watch } = await import('./watch-cli');
    watch(command);
  } else {
    //非觀察模式
    try {
      const { options, warnings } = await getConfigs(command);
      try {
        //因?yàn)榕渲梦募梢苑祷匾粋€數(shù)組,所以需要挨個執(zhí)行
        for (const inputOptions of options) {
          //內(nèi)部執(zhí)行 rollup(inputOptions) 進(jìn)行打包
          await build(inputOptions, warnings, command.silent);
        }
        if (command.failAfterWarnings && warnings.warningOccurred) {
          warnings.flush();
          handleError(errorFailAfterWarnings());
        }
      } catch (error: any) {
        warnings.flush();
        handleError(error);
      }
    } catch (error: any) {
      handleError(error);
    }
  }
}
async function getConfigs(
  command: any
): Promise<{ options: MergedRollupOptions[]; warnings: BatchWarnings }> {
  if (command.config) {
    //獲取配置文件
    const configFile = await getConfigPath(command.config);
    //讀取配置文件獲取配置項(xiàng)
    const { options, warnings } = await loadConfigFile(configFile, command);
    return { options, warnings };
  }
  return await loadConfigFromCommand(command);
}

打包生成 rollup 文件

在 rollup.config.ts 文件中有導(dǎo)出一個方法:

//rollup.config.ts
export default async function (
  command: Record<string, unknown>
): Promise<RollupOptions | RollupOptions[]> {
  const { collectLicenses, writeLicense } = getLicenseHandler(
    fileURLToPath(new URL('.', import.meta.url))
  );
  const commonJSBuild: RollupOptions = {
    // 'fsevents' is a dependency of 'chokidar' that cannot be bundled as it contains binary code
    external: ['fsevents'],
    input: {
      'loadConfigFile.js': 'cli/run/loadConfigFile.ts',
      'rollup.js': 'src/node-entry.ts'
    },
    onwarn,
    output: {
      banner: getBanner,
      chunkFileNames: 'shared/[name].js',
      dir: 'dist',
      entryFileNames: '[name]',
      exports: 'named',
      externalLiveBindings: false,
      format: 'cjs',
      freeze: false,
      generatedCode: 'es2015',
      interop: 'default',
      manualChunks: { rollup: ['src/node-entry.ts'] },
      sourcemap: true
    },
    plugins: [
      ...nodePlugins,
      addCliEntry(), //添加cli入口文件
      esmDynamicImport(),
      !command.configTest && collectLicenses(),
      !command.configTest && copyTypes('rollup.d.ts')
    ],
    strictDeprecations: true,
    treeshake
  };
  /**
	 * 
	當(dāng)我們執(zhí)行npm run build 的時候就相當(dāng)于執(zhí)行了 rollup --config rollup.config.ts --configPlugin typescript
	此時 command 就是如下對象:
	{ 
		_: [],
		config: 'rollup.config.ts',
		c: 'rollup.config.ts',     
		configPlugin: 'typescript' 
	}
	*/
  if (command.configTest) {
    return commonJSBuild;
  }
  const esmBuild: RollupOptions = {
    ...commonJSBuild,
    input: { 'rollup.js': 'src/node-entry.ts' },
    output: {
      ...commonJSBuild.output,
      dir: 'dist/es',
      format: 'es',
      minifyInternalExports: false,
      sourcemap: false
    },
    plugins: [...nodePlugins, emitModulePackageFile(), collectLicenses(), writeLicense()]
  };
  const { collectLicenses: collectLicensesBrowser, writeLicense: writeLicenseBrowser } =
    getLicenseHandler(fileURLToPath(new URL('browser', import.meta.url)));
  const browserBuilds: RollupOptions = {
    input: 'src/browser-entry.ts',
    onwarn,
    output: [
      {
        banner: getBanner,
        file: 'browser/dist/rollup.browser.js',
        format: 'umd',
        name: 'rollup',
        plugins: [copyTypes('rollup.browser.d.ts')],
        sourcemap: true
      },
      {
        banner: getBanner,
        file: 'browser/dist/es/rollup.browser.js',
        format: 'es',
        plugins: [emitModulePackageFile()]
      }
    ],
    plugins: [
      replaceBrowserModules(),
      alias(moduleAliases),
      nodeResolve({ browser: true }),
      json(),
      commonjs(),
      typescript(),
      terser({ module: true, output: { comments: 'some' } }),
      collectLicensesBrowser(),
      writeLicenseBrowser(),
      cleanBeforeWrite('browser/dist')
    ],
    strictDeprecations: true,
    treeshake
  };
  return [commonJSBuild, esmBuild, browserBuilds];
}

請注意上面使用了 addCliEntry 插件。它的代碼定義在 build-plugins/add-cli-entry.ts:

import { chmod } from 'node:fs/promises';
import { resolve } from 'node:path';
import MagicString from 'magic-string';
import type { Plugin } from 'rollup';
const CLI_CHUNK = 'bin/rollup';
export default function addCliEntry(): Plugin {
  return {
    buildStart() {
      this.emitFile({
        fileName: CLI_CHUNK,
        id: 'cli/cli.ts',
        preserveSignature: false,
        type: 'chunk'
      });
    },
    name: 'add-cli-entry',
    renderChunk(code, chunkInfo) {
      if (chunkInfo.fileName === CLI_CHUNK) {
        const magicString = new MagicString(code);
        //聲明在 shell 中使用 node來運(yùn)行
        magicString.prepend('#!/usr/bin/env node\n\n');
        return { code: magicString.toString(), map: magicString.generateMap({ hires: true }) };
      }
      return null;
    },
    writeBundle({ dir }) {
      return chmod(resolve(dir!, CLI_CHUNK), '755'); //修改文件可讀寫權(quán)限,保證執(zhí)行的權(quán)限
      /*
			在Node.js中,可以調(diào)用fs模塊,有一個方法chmod,可以用來修改文件或目錄的讀寫權(quán)限。方法chmod有三個參數(shù),文件路徑、讀寫權(quán)限和回調(diào)函數(shù),其中讀寫權(quán)限是用代號表示的,
			(1)0600:所有者可讀寫,其他的用戶不行
			(2)0644:所有者可讀寫,其他的用戶只讀
			(3)0740:所有者可讀寫,所有者所在的組只讀
			(4)0755:所有者可讀寫,其他用戶可讀可執(zhí)行
			*/
    }
  };
}

addCliEntry 插件將 /cli/cli.ts 源碼添加到輸出的 chunk 中,并且在文件的頭部增加一行代碼:'#!/usr/bin/env node\n\n'。

首先解釋一下 #!/usr/bin/env node

  • # 在 shell 腳本中單獨(dú)使用代表注釋
  • #! 組合使用表示要用在 shell 腳本中
  • env 是 Mac 或者 Linux 系統(tǒng)的環(huán)境變量,是一個可執(zhí)行命令
  • env node : 指的是使用當(dāng)前 env 環(huán)境內(nèi)的配置的 Path 路徑下的 node 執(zhí)行
  • 當(dāng)前腳本在執(zhí)行 shell 時,會自動從 env 內(nèi)調(diào)用合適的解釋器執(zhí)行

這樣做的目的是為了能夠解析當(dāng)前腳本文件,該命令會自動從當(dāng)前 env 環(huán)境中查找配置的 node 版本來執(zhí)行腳本。

最終我們使用 npm run build 的命令打包 rollup 源碼的時候就會生成 dist/bin/rollup 這個文件了

#!/usr/bin/env node
/*
  @license
	Rollup.js v3.2.3
	Sat, 28 Jan 2023 07:43:49 GMT - commit 5fa73d941c16a6bcbebaa3ae5bb6aaca8b97d0b7
	https://github.com/rollup/rollup
	Released under the MIT License.
*/
'use strict';
Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: 'Module' } });
const process$1 = require('node:process');
const rollup = require('../shared/rollup.js');
const require$$2 = require('util');
const require$$0 = require('path');
const require$$0$1 = require('fs');
const node_fs = require('node:fs');
const node_path = require('node:path');
const loadConfigFile_js = require('../shared/loadConfigFile.js');
require('node:perf_hooks');
require('node:crypto');
require('node:events');
require('tty');
require('node:url');
# ...
const command = argParser(process$1.argv.slice(2), {
    alias: rollup.commandAliases,
    configuration: { 'camel-case-expansion': false } //為 argParser 解析器提供配置選項(xiàng), 'camel-case-expansion': false 表示連字符參數(shù)不會被擴(kuò)展為駝峰大小寫別名
});
//process.stdin.isTTY 用于檢測我們的程序是否直接連到終端
if (command.help || (process$1.argv.length <= 2 && process$1.stdin.isTTY)) {
    console.log(`\n${help.replace('__VERSION__', rollup.version)}\n`);
}
else if (command.version) {
    console.log(`rollup v${rollup.version}`);
}
else {
  try {
      // eslint-disable-next-line unicorn/prefer-module
      //瀏覽器是支持source maps的,但node環(huán)境原生不支持source maps。所以我們可以通過'source-map-support'包來實(shí)現(xiàn)這個功能。這樣當(dāng)程序執(zhí)行出錯的時候方便通過控制臺定位到源碼位置。
      require('source-map-support').install();
  }
  catch {
      // do nothing
  }
  runRollup(command);
}
exports.getConfigPath = getConfigPath;
exports.loadConfigFromCommand = loadConfigFromCommand;
exports.prettyMilliseconds = prettyMilliseconds;
exports.printTimings = printTimings;
//# sourceMappingURL=rollup.map

以上就是rollup cli開發(fā)全面系統(tǒng)性rollup源碼分析的詳細(xì)內(nèi)容,更多關(guān)于rollup cli開發(fā)源碼分析的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 數(shù)據(jù)結(jié)構(gòu)TypeScript之鏈表實(shí)現(xiàn)詳解

    數(shù)據(jù)結(jié)構(gòu)TypeScript之鏈表實(shí)現(xiàn)詳解

    這篇文章主要為大家介紹了數(shù)據(jù)結(jié)構(gòu)TypeScript之鏈表實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-01-01
  • 前端構(gòu)建 Less入門(CSS預(yù)處理器)

    前端構(gòu)建 Less入門(CSS預(yù)處理器)

    眾多CSS預(yù)處理器中Less的語法最接近原生CSS,因此相對來說更容易上手,假如有JS、C#等編程經(jīng)驗(yàn)的話,其實(shí)上述的幾種預(yù)處理器的學(xué)習(xí)成本也不會特別高。下面是我們這陣子的學(xué)習(xí)筆記,以便日后查閱
    2017-03-03
  • 數(shù)據(jù)結(jié)構(gòu)TypeScript之棧和隊(duì)列詳解

    數(shù)據(jù)結(jié)構(gòu)TypeScript之棧和隊(duì)列詳解

    這篇文章主要介紹了數(shù)據(jù)結(jié)構(gòu)TypeScript之棧和隊(duì)列詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-01-01
  • 基于Javascript實(shí)現(xiàn)頁面商品個數(shù)增減功能

    基于Javascript實(shí)現(xiàn)頁面商品個數(shù)增減功能

    本文給大家介紹基于Javascript實(shí)現(xiàn)頁面商品個數(shù)增減功能,通過點(diǎn)擊數(shù)量增減個數(shù),代碼分為前端頁面,后臺返回代碼,代碼簡單易懂,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2019-07-07
  • TypeScript?中?as?const使用介紹

    TypeScript?中?as?const使用介紹

    這篇文章主要為大家介紹了TypeScript?中?as?const使用介紹,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12
  • typescript封裝消息提示框插件ew-message使用實(shí)戰(zhàn)

    typescript封裝消息提示框插件ew-message使用實(shí)戰(zhàn)

    這篇文章主要為大家介紹了typescript封裝消息提示框插件ew-message使用實(shí)戰(zhàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-11-11
  • typescript類型體操及關(guān)鍵字使用示例詳解

    typescript類型體操及關(guān)鍵字使用示例詳解

    這篇文章主要為大家介紹了typescript類型體操及關(guān)鍵字使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-11-11
  • layui中的layer彈出層自定義樣式更改背景

    layui中的layer彈出層自定義樣式更改背景

    layui中的layer彈出層有很多提示框,但是我們使用最多的應(yīng)該就幾種,layer.msg、layer.alert、layer.open、layer.load等。layer?有內(nèi)置的skin:layui-layer-lan,layui-layer-molv,可以直接使用。skin不僅允許你傳入layer內(nèi)置的樣式class名,可以自定義class名。
    2023-06-06
  • TypeScript 基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)哈希表 HashTable教程

    TypeScript 基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)哈希表 HashTable教程

    這篇文章主要為大家介紹了TypeScript 基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)哈希表 HashTable教程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-02-02
  • TypeScript 高級數(shù)據(jù)類型實(shí)例詳解

    TypeScript 高級數(shù)據(jù)類型實(shí)例詳解

    這篇文章主要為大家介紹了TypeScript 高級數(shù)據(jù)類型實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-01-01

最新評論

马关县| 柏乡县| 张北县| 仁布县| 龙里县| 开阳县| 时尚| 中方县| 敦煌市| 五原县| 永仁县| 民和| 绥芬河市| 荔浦县| 盘山县| 黄浦区| 南陵县| 米易县| 五常市| 奈曼旗| 公安县| 象州县| 邹平县| 永兴县| 青阳县| 光泽县| 温州市| 山西省| 徐汇区| 曲靖市| 苏尼特左旗| 临沂市| 方正县| 榆中县| 衡山县| 武平县| 会东县| 淳安县| 叶城县| 昭苏县| 金阳县|