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ì)象,然而并不可以
__WEBPACK_IMPORTED_MODULE_18_moment___default()()
調(diào)用了兩次才拿到
onTimeChange = val => {
const mon = moment //這邊通過
console.info(val)
}
從上圖可知 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
由圖可以看出來节猿,script標(biāo)簽插入HTML字符串是在afterTemplateExecution
跟beforeEmit
之間完成的票从,做了代碼測試看到的結(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è)階段的亿扁。
pitching 階段:loader 上的 pitch 方法捺典,按照
后置(post)、行內(nèi)(normal)从祝、普通(inline)襟己、前置(pre)
的順序調(diào)用。更多詳細(xì)信息牍陌,請(qǐng)查看 越過 loader(pitching loader)擎浴。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
是個(gè)對(duì)象售葡,存了文件名跟對(duì)應(yīng)文件內(nèi)容的字符串。
HtmlWebpackPlugin index.js
emitHtmlPromise.then(() => {
callback();//出發(fā)了callAsync的回調(diào)
});
Compiler.js emitAssets方法
這里調(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ì)象里
字符串轉(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是怎樣監(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í)行的嗎桂肌?有什么先后順序嗎?