webpack 知識(shí)點(diǎn)

1.webpack 模塊化實(shí)現(xiàn)

項(xiàng)目里面引入了moment

import moment from 'moment'

在非debug狀態(tài)下,嘗試輸入moment是拿不到moment這個(gè)模塊的椰弊,但是debug代碼的時(shí)候不能直接拿到moment這個(gè)模塊囚痴,而是通過__WEBPACK_IMPORTED_MODULE_18_moment___default可以拿到
按理說moment()調(diào)用一次就拿到了moment對(duì)象,然而并不可以

moment

__WEBPACK_IMPORTED_MODULE_18_moment___default()()調(diào)用了兩次才拿到

  onTimeChange = val => {
    const mon = moment //這邊通過
    console.info(val)
  }
debug

從上圖可知 debug 狀態(tài)還是無法再控制臺(tái)輸出moment(),而賦值給變量mon就可以執(zhí)行mon()拿到moment對(duì)象袱结,而__WEBPACK_IMPORTED_MODULE_18_moment___default()()還是要執(zhí)行兩次才能拿到moment對(duì)象

webpack務(wù)虛掃盲
從 Bundle 文件看 Webpack 模塊機(jī)制
webpack模塊加載原理

2. require.context

官網(wǎng)的解釋:

require.context(directory:String, includeSubdirs:Boolean /* optional, default true */, filter:RegExp /* optional */)

Specify a whole group of dependencies using a path to the directory
, an option to includeSubdirs
, and a filter
for more fine grained control of the modules included. These can then be easily resolved later on:

var context = require.context('components', true, /\.html$/);//獲取components目錄下所有以.html結(jié)尾的文件(包括其子目錄)
var componentA = context.resolve('componentA');

使用webpack的require.context實(shí)現(xiàn)路由“去中心化”管理
webpack require context 說明

3. 使用ejs作為HtmlWebpackPlugin的模板

通常情況下我們使用HTML作為單頁面的模板作瞄,

  new HtmlWebpackPlugin({  // Also generate a test.html
      filename: 'test.html',
      template: 'src/assets/test.html'
    })

但是有時(shí)候我們需要在頁面用到變量或者遍歷數(shù)組恢着,那么就需要使用模板引擎桐愉,比如ejs,

  <script src="<%= htmlWebpackPlugin.options.devServer %>/webpack-dev-server.js" type="text/javascript"></script>

上面的htmlWebpackPlugin.options.devServer變量 來自于webpack.config.js

 new HtmlWebpackPlugin({
      inject: false,
      template: '../index.ejs',
      appMountId: 'app',
      devServer: 'http://localhost:3001', // 看這里
      mobile: true,
      lang: 'en-US',
      title: 'My App',
      window: {
        env: {
          apiHost: 'http://myapi.com/api/v1'
        }
      }
    })
  ]

具體配置參考jaketrent/html-webpack-template
小白學(xué)react之EJS模版實(shí)戰(zhàn)

4. vscode debug webpack

我們?cè)趩?dòng)項(xiàng)目的時(shí)候都是使用npm script啟動(dòng)的掰派,那么怎么用vscode啟動(dòng)項(xiàng)目并debug webpack的打包呢从诲?

首先配置vscode launch.json

    {
      "type": "node",
      "request": "launch",
      "name": "Launch via NPM with dll",
      "runtimeExecutable": "npm",
      "runtimeArgs": ["run-script", "dll"],
      "port": 9229
    },

一開始做的嘗試node --inspect-brk ./node_modules/.bin/webpack --config config/webpack.config.dll.js但是報(bào)錯(cuò)

basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")           ^^^^^^^  SyntaxError: missing ) after argument list

看了這篇issue node --inspect-brk ./node_modules/.bin/webpack --config config/webpack.config.dll.js 報(bào)錯(cuò)解決辦法 修改為node --inspect-brk ./node_modules/webpack/bin/webpack --config config/webpack.config.dll.js
這樣就可以調(diào)試了,哈哈哈

報(bào)錯(cuò)的原因好像是操作系統(tǒng)的問題靡羡,.bin目錄下是shell和cmd文件系洛,直接node --inspect-brk ./node_modules/.bin/webpack就執(zhí)行shell俊性,而在Windows上執(zhí)行node --inspect-brk ./node_modules/.bin/webpack.cmd也會(huì)報(bào)錯(cuò)

SyntaxError: missing ) after argument list

我的項(xiàng)目是用create-react-app創(chuàng)建的,并且做了eject描扯,然后webpack的配置全部暴露出來了定页,啟動(dòng)項(xiàng)目"start": "node scripts/start.js", 嘗試上面的方法配置webpack,debug會(huì)報(bào)錯(cuò)绽诚,然后重新做了配置"start:debug": "node --nolazy --inspect-brk=9229 scripts/start.js",典徊,vscode launch.json加上如下代碼,

    {
      "type": "node",
      "request": "launch",
      "name": "Launch via NPM with start",
      "runtimeExecutable": "npm",
      "runtimeArgs": ["run-script", "start:debug"],
      "port": 9229
    },

這樣不但start.js webpack.config.dev.js斷點(diǎn)能進(jìn)入恩够,webpack的loader也可以斷點(diǎn)進(jìn)入卒落,比如css-loader,但是去掉所有的斷點(diǎn),然后啟動(dòng)蜂桶,要過很久才能加載出頁面导绷。

5. style-resources-loader@1.2.1導(dǎo)致cra創(chuàng)建的typescript項(xiàng)目編譯很卡

我用cra新建的項(xiàng)目的webpack的版本是4.39.1,style-resources-loader@1.2.1依賴的webpack的版本是^4.16.5屎飘,改變一行代碼,編譯下來要20s,升級(jí)style-resources-loader到1.3.3之后贾费,編譯只要5秒的樣子钦购,提升了幾倍。style-resources-loader@1.3.3 依賴webpack@4.41.3,我項(xiàng)目里安裝的是webpack@4.39.1褂萧,不曉得都是webpack@4.x為啥編譯速度差別這么大.
刨根問底押桃,在style-resources-loaderissue找到了答案,在我的mac上編譯是不卡的导犹,但是在windows上很卡唱凯,Failed to cache .less files on Win 10 with Webpack 4 #17是因?yàn)閣ebpack升級(jí)到4有些代碼改了,而該庫沒有做出對(duì)不同系統(tǒng)文件系統(tǒng)路徑兼容的處理谎痢,導(dǎo)致代碼編譯的樣式?jīng)]有被緩存磕昼,是的每次rebuild都會(huì)去重新編譯樣式文件。

6. 關(guān)于webpack是怎么將css 跟js是怎么插入到dom中的

cra 新建的項(xiàng)目dev環(huán)境默認(rèn)有三個(gè)文件,webpack@4.42.0


dev環(huán)境的三個(gè)js文件
html-webpack-plugin flow

由圖可以看出來节猿,script標(biāo)簽插入HTML字符串是在afterTemplateExecutionbeforeEmit之間完成的票从,做了代碼測試看到的結(jié)果也是如此,扒開源碼看看

文件名 作用 解釋
bundle.js webpack runtime代碼 包括webpack-dev-server
0.chunk.js 第三方庫代碼 react react-dom等
main.chunk.js 自己寫的代碼 包括自己寫的js跟css
  • webpack 怎樣把樣式代碼插入到j(luò)s中的

  • webpack怎樣實(shí)現(xiàn)對(duì)樣式文件的緩存的

  • 是style-loader還是html-webpack-plugin將style標(biāo)簽插入到dom中的

style-loader@1.0.0源碼各種拼接字符串還是很難讀的滨嘱,找到一篇不錯(cuò)的源碼解析文章webpack loader 從上手到理解系列 style-loader

{loaderUtils.stringifyRequest(
        this,
        `!!${request}`
      )}

!!從未見過峰鄙,在js里是將其他類型轉(zhuǎn)為boolean,官方文檔顯示,有三種使用 loader 的方式

  • 配置(推薦):在 webpack.config.js 文件中指定 loader太雨。
  • 內(nèi)聯(lián):在每個(gè) import 語句中顯式指定 loader吟榴。
  • CLI:在 shell 命令中指定它們。

內(nèi)聯(lián)(inline)

可以在 import 語句或任何 等同于 "import" 的方法 中指定 loader囊扳。使用 ! 將資源中的 loader 分開吩翻。每個(gè)部分都會(huì)相對(duì)于當(dāng)前目錄解析兜看。

import Styles from 'style-loader!css-loader?modules!./styles.css';

使用 ! 或者!或者-!為整個(gè)規(guī)則添加前綴,可以覆蓋webpack.config.js配置中的所有 loader 定義仿野。具體規(guī)則如下:

  • Prefixing with ! will disable all configured normal loaders
import Styles from '!style-loader!css-loader?modules!./styles.css';
  • Prefixing with !! will disable all configured loaders (preLoaders, loaders, postLoaders)
import Styles from '!!style-loader!css-loader?modules!./styles.css';
  • Prefixing with -! will disable all configured preLoaders and loaders but not postLoaders
import Styles from '-!style-loader!css-loader?modules!./styles.css';

這邊提到了preLoaders 糟需,postLoaders ,在webpack 1.0是有這個(gè)配置的域仇,v2就拿掉了匈辱,但是概念還是在的,變成了enforce配置球涛,如下:

  module: {
-   preLoaders: [
+   rules: [
      {
        test: /\.js$/,
+       enforce: "pre",
        loader: "eslint-loader"
      }
    ]
  }

pre將該loader設(shè)置到最前面劣针,post將該loader設(shè)置到最后面。但是loader的執(zhí)行是有兩個(gè)階段的亿扁。

  1. pitching 階段:loader 上的 pitch 方法捺典,按照 后置(post)、行內(nèi)(normal)从祝、普通(inline)襟己、前置(pre) 的順序調(diào)用。更多詳細(xì)信息牍陌,請(qǐng)查看 越過 loader(pitching loader)擎浴。

  2. normal階段:loader 上的 常規(guī)方法,按照 前置(pre)毒涧、行內(nèi)(normal)贮预、普通(inline)、后置(post) 的順序調(diào)用契讲。模塊源碼的轉(zhuǎn)換仿吞,發(fā)生在這個(gè)階段。

官方文檔舉了兩個(gè)階段的例子

module.exports = {
  //...
  module: {
    rules: [
      {
        //...
        use: [
          'a-loader',
          'b-loader',
          'c-loader'
        ]
      }
    ]
  }
};

將會(huì)發(fā)生這些步驟:

|- a-loader `pitch`
  |- b-loader `pitch`
    |- c-loader `pitch`
      |- requested module is picked up as a dependency
    |- c-loader normal execution
  |- b-loader normal execution
|- a-loader normal execution

這也就解釋了style-loader為什么代碼都寫在module.exports.pitch中而且module.exports返回的是空函數(shù)捡偏。

  • webpack怎樣將js插入到dom里的
    html-webpack-plugin的index.js -> postProcessHtml函數(shù) -> injectAssetsIntoHtml函數(shù) -> 賦值給webpack的compilation.assets
    整個(gè)流程HTML都是字符串唤冈,然后HTML字符串交給了webpack的Compiler.js處理

  • html字符串是怎樣寫入到文件里的?

content = Buffer.from(bufferOrString, "utf8")//將字符串轉(zhuǎn)為Buffer
this.outputFileSystem.writeFile(targetPath, content, err => {})//outputFileSystem為fs或者memory-fs,content為Buffer

就這兩個(gè)重要步驟

html-webpacl-plugin apply方法银伟,官方注釋已經(jīng)寫得很明確

        const injectedHtmlPromise = Promise.all([assetTagGroupsPromise, templateExectutionPromise])
          // Allow plugins to change the html before assets are injected
          .then(([assetTags, html]) => {
            const pluginArgs = { html, headTags: assetTags.headTags, bodyTags: assetTags.bodyTags, plugin: self, outputName: childCompilationOutputName };
            return getHtmlWebpackPluginHooks(compilation).afterTemplateExecution.promise(pluginArgs);
          })
          .then(({ html, headTags, bodyTags }) => {
            return self.postProcessHtml(html, assets, { headTags, bodyTags });//將<srcipt src="xxxx">標(biāo)簽插入HTML字符串
          });

        const emitHtmlPromise = injectedHtmlPromise
          // Allow plugins to change the html after assets are injected
          .then((html) => {
            const pluginArgs = { html, plugin: self, outputName: childCompilationOutputName };
            return getHtmlWebpackPluginHooks(compilation).beforeEmit.promise(pluginArgs)
              .then(result => result.html);
          })
          .catch(err => {
            // In case anything went wrong the promise is resolved
            // with the error message and an error is logged
            compilation.errors.push(prettyError(err, compiler.context).toString());
            // Prevent caching
            self.hash = null;
            return self.options.showErrors ? prettyError(err, compiler.context).toHtml() : 'ERROR';
          })
          .then(html => {
            // Allow to use [templatehash] as placeholder for the html-webpack-plugin name
            // See also https://survivejs.com/webpack/optimizing/adding-hashes-to-filenames/
            // From https://github.com/webpack-contrib/extract-text-webpack-plugin/blob/8de6558e33487e7606e7cd7cb2adc2cccafef272/src/index.js#L212-L214
            const finalOutputName = childCompilationOutputName.replace(/\[(?:(\w+):)?templatehash(?::([a-z]+\d*))?(?::(\d+))?\]/ig, (_, hashType, digestType, maxLength) => {
              return loaderUtils.getHashDigest(Buffer.from(html, 'utf8'), hashType, digestType, parseInt(maxLength, 10));
            });
              // Add the evaluated html code to the webpack assets
            compilation.assets[finalOutputName] = {
              source: () => html,//此處的HTML任然為字符串
              size: () => html.length
            };
            return finalOutputName;
          })
          .then((finalOutputName) => getHtmlWebpackPluginHooks(compilation).afterEmit.promise({
            outputName: finalOutputName,
            plugin: self
          }).catch(err => {
            console.error(err);
            return null;
          }).then(() => null));

再次驗(yàn)證script標(biāo)簽是在afterTemplateExecution跟beforeEmit插入到HTML字符串中的务傲,但是沒有寫入到文件中,只是將拼接好的HTML字符串枣申,交給了webpack

compilation.assets

compilation.assets是個(gè)對(duì)象售葡,存了文件名跟對(duì)應(yīng)文件內(nèi)容的字符串。
HtmlWebpackPlugin index.js

        emitHtmlPromise.then(() => {
          callback();//出發(fā)了callAsync的回調(diào)
        });

Compiler.js emitAssets方法


image.png

這里調(diào)用了memory-fs的mkdirp創(chuàng)建目錄忠藤,然后根據(jù)文件分別新建對(duì)應(yīng)的文件

// Write the file to output file system
this.outputFileSystem.writeFile(targetPath, content, err => {})

而memory-fswriteFileSync就是將文件用二進(jìn)制的形式存在了current對(duì)象里

writeFileSync

字符串轉(zhuǎn)為二進(jìn)制對(duì)象content = Buffer.from(bufferOrString, "utf8");至于怎樣將這個(gè)對(duì)象顯示在chrome的source面板中的挟伙,有空再探索。

webpacl Compilerthis.outputFileSystem的實(shí)例屬性默認(rèn)為null,start.js新建devServer const devServer = new WebpackDevServer(compiler, serverConfig);傳遞webpack compiler實(shí)例到dev server 尖阔,然后dev server再將compiler傳遞到dev middleware 贮缅,webpack-dev-middleware@3.7.2 fs.js setFs函數(shù)中給outputFileSystem賦值

const MemoryFileSystem = require('memory-fs');

      fileSystem = new MemoryFileSystem();

      // eslint-disable-next-line no-param-reassign
      compiler.outputFileSystem = fileSystem;

同樣我們?nèi)绻趙ebpack-dev-server配置writeToDisk:true,fs.js文件的toDisk函數(shù)調(diào)用fs.writeFile將文件寫入磁盤

const fs = require('fs');

              return fs.writeFile(targetPath, content, (writeFileError) => {
                   return callback();
              });

在構(gòu)建的時(shí)候沒有dev server是怎樣將文件寫入磁盤的?

const webpack = (options, callback) => {
    let compiler;
           if (typeof options === "object") {
        compiler = new Compiler(options.context);
        compiler.options = options;
        new NodeEnvironmentPlugin({
            infrastructureLogging: options.infrastructureLogging
        }).apply(compiler);
        if (options.plugins && Array.isArray(options.plugins)) {
            for (const plugin of options.plugins) {
                if (typeof plugin === "function") {
                    plugin.call(compiler, compiler);
                } else {
                    plugin.apply(compiler);//調(diào)用plugin的apply函數(shù)
                }
            }
        }
    } 
    return compiler;
};

NodeEnvironmentPlugin.js

class NodeEnvironmentPlugin {
    constructor(options) {
        this.options = options || {};
    }

    apply(compiler) {
        ...
        compiler.inputFileSystem = new CachedInputFileSystem(
            new NodeJsInputFileSystem(),
            60000
        );
        const inputFileSystem = compiler.inputFileSystem;
        compiler.outputFileSystem = new NodeOutputFileSystem();//給outputFileSystem賦值
        compiler.watchFileSystem = new NodeWatchFileSystem(
            compiler.inputFileSystem
        );
        ...
    }
}
module.exports = NodeEnvironmentPlugin;

NodeOutputFileSystem.js

const fs = require("fs");
const path = require("path");
const mkdirp = require("mkdirp");

class NodeOutputFileSystem {
    constructor() {
        this.mkdirp = mkdirp;
        this.mkdir = fs.mkdir.bind(fs);
        this.rmdir = fs.rmdir.bind(fs);
        this.unlink = fs.unlink.bind(fs);
        this.writeFile = fs.writeFile.bind(fs);//其實(shí)就是調(diào)用node原生的fs
        this.join = path.join.bind(path);
    }
}

module.exports = NodeOutputFileSystem;

最后在Compiler.js line 434介却,調(diào)用node的 fs.writeFile函數(shù)寫入文件到磁盤

// Write the file to output file system
this.outputFileSystem.writeFile(targetPath, content, err => {})

倉庫地址 webpack-speed-measure

webpack是怎樣監(jiān)測文件的變化的谴供?

loader 在什么時(shí)候執(zhí)行的?
loader-runner負(fù)責(zé)執(zhí)行l(wèi)oader

你真的掌握了loader么齿坷?- loader十問
從零實(shí)現(xiàn)一個(gè) Webpack Loader

plugin跟loader是并行執(zhí)行的嗎桂肌?有什么先后順序嗎?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末永淌,一起剝皮案震驚了整個(gè)濱河市崎场,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌遂蛀,老刑警劉巖谭跨,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異李滴,居然都是意外死亡螃宙,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門所坯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來污呼,“玉大人,你說我怎么就攤上這事包竹。” “怎么了籍凝?”我有些...
    開封第一講書人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵周瞎,是天一觀的道長。 經(jīng)常有香客問我饵蒂,道長声诸,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任退盯,我火速辦了婚禮彼乌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘渊迁。我一直安慰自己慰照,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開白布琉朽。 她就那樣靜靜地躺著毒租,像睡著了一般。 火紅的嫁衣襯著肌膚如雪箱叁。 梳的紋絲不亂的頭發(fā)上墅垮,一...
    開封第一講書人閱讀 51,182評(píng)論 1 299
  • 那天惕医,我揣著相機(jī)與錄音,去河邊找鬼算色。 笑死抬伺,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的灾梦。 我是一名探鬼主播峡钓,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼斥废!你這毒婦竟也來了椒楣?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤牡肉,失蹤者是張志新(化名)和其女友劉穎捧灰,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體统锤,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡毛俏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了饲窿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片煌寇。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖逾雄,靈堂內(nèi)的尸體忽然破棺而出阀溶,到底是詐尸還是另有隱情,我是刑警寧澤鸦泳,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布银锻,位于F島的核電站,受9級(jí)特大地震影響做鹰,放射性物質(zhì)發(fā)生泄漏击纬。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一钾麸、第九天 我趴在偏房一處隱蔽的房頂上張望更振。 院中可真熱鬧,春花似錦饭尝、人聲如沸肯腕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽乎芳。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間奈惑,已是汗流浹背吭净。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留肴甸,地道東北人寂殉。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像原在,于是被迫代替她去往敵國和親友扰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容