讀懂這些題,你就是webpack配置工程師

本篇文章解答了60多道關於webpack的問題,答案有詳細有簡單,時間關係,有的問題還未作出解答,後續會補上,持續更新。若是答案有不對的地方,請輕拍。javascript

一、全局安裝和本地安裝,開發依賴和項目依賴有什麼區別

1-一、全局安裝

全局安裝webpack,意味着webpack包被安裝到npm的全局包目錄裏了。
查看npm全局包位置:css

npm root -g
複製代碼

附加點npm的知識:
html

查看npm配置信息:vue

npm config ls
複製代碼

查看npm下載源:java

npm config get registry
複製代碼

查看npm安裝目錄:node

npm config get prefix
複製代碼

這裏假設經過命令npm config get prefix獲取到的路徑爲C:\Users\wz\AppData\Roaming\npm ,爲了方便起見,用A代替C:\Users\wz\AppData\Roaming
經過這個目錄能夠獲取到如下信息:
react

  • 一、npm全局配置文件(A\npm\etc)
  • 二、全局安裝的包(A\npm\node_modules)
  • 三、全局命令(npx link生成的命令)(A\npm)
  • 四、npm緩存(A\npm-cache)

1-二、本地安裝

本地安裝webpackwebpack包被安裝到了項目根目錄下的node_modules裏了。jquery

1-三、開發依賴

開發依賴,指的是隻有在項目開發、編譯、打包、壓縮等過程纔會用到的包,一旦文件成功產出,就再也不須要他們了,好比:less-loaderwebpack等等。webpack

1-四、項目依賴

項目依賴,指的是從開始編寫項目到項目上線之後都須要的包,好比:vueaxios等等。ios

二、使用webpack須要安裝哪些模塊

須要安裝:

  1. webpackwebpack核心包)
  2. webpack-cliwebpack命令行工具,依賴於webpack核心包)

三、webpack默認配置文件(2種)的名字是什麼

webpack默認配置文件:
第一種:webpack.config.js
第二種:webpackfile.js

webpack源碼中和配置相關的代碼,位置:webpack/bin/convert-argv.js

//...
//從這裏能夠看出webpack的2中配置文件
var defaultConfigFiles = ["webpack.config", "webpackfile"].map(function(filename) {
		return extensions.map(function(ext) {
			return {
				path: path.resolve(filename + ext),
				ext: ext
			};
		});
	}).reduce(function(a, i) {
		return a.concat(i);
	}, []);

//...
複製代碼

四、webpack打包後的結果爲何能夠在瀏覽器中執行

爲何會在瀏覽器裏執行,就要從webpack打包後的文件中去找答案了。
搭建一個簡單的項目,只安裝webpack這個包,項目目錄以下:

index.js以下:

function foo() {
    console.log(window.localStorage)
}
module.exports = { foo }
複製代碼

webpack.config.js以下:

module.exports = {
    mode:'development',
    entry: './src/index.js',
    output: {
        path: require('path').resolve(__dirname, 'dist'),
        filename: 'bundle.js'
    } 
}
複製代碼

在命令行執行webpack命令後,打包後的文件內容,通過刪減,整體"框架"代碼以下:

(function (modules) {
        var installedModules = {};
        function __webpack_require__(moduleId) {
            if (installedModules[moduleId]) {
                return installedModules[moduleId].exports;

            }
            var module = installedModules[moduleId] = {
                i: moduleId,
                l: false,
                exports: {}

            };
            modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
            module.l = true;
            return module.exports;

        }
        return __webpack_require__("./src/index.js");
    })({
        "./src/index.js": (function (module, exports) {
            eval(/* ... */)
        })
    })
複製代碼

分析以上代碼:
一、整體是一個自執行函數,雙括號形式。

(function(modules){/* ... */})({})
複製代碼

一個自執行函數固然能夠在瀏覽器中執行了。

二、自執行函數的函數體內,定義了模塊緩存對象installedModules,定義了模塊加載方法__webpack_require__,這個__webpack_require__方法就是爲瀏覽器量身打造的,做用至關於node中的require方法。__webpack_require__方法的邏輯也不難理解,首先經過模塊名(其實就是一個路徑)去模塊緩存對象上查找,找不到的話,就新建一個模塊module並緩存到installedModules上,而後經過模塊名找到對應的模塊函數,執行它,並將module等參數傳入,最後返回模塊導出對象module.exports。這段代碼建議仔細看看
三、自執行函數的參數。該參數是一個對象,相似下面這樣:

{
    "./src/index.js": (function (module, exports) {eval(/* ... */)})
}
複製代碼

該對象的鍵是一個路徑字符串,其實就是咱們調用require方法時傳入的模塊路徑; 值爲一個接收moduleexports參數的函數,函數體內是一個包裹着一堆字符串代碼的eval函數,這一堆字符串代碼就是咱們寫的代碼。可見,webpack爲了讓咱們的代碼可以在瀏覽器裏執行,作了多少工做。

參考文章

五、webpack有幾種模式,對應模式的做用

webpack有3種模式:

  1. development
  2. production
  3. none

簡單來講,不一樣的模式下,webpack會啓用不一樣的插件,或者不一樣的優化手段。
development模式下,會啓用如下插件:

  • new webpack.NamedModulesPlugin()
  • new webpack.NamedChunksPlugin()

production模式下,會啓用如下插件:

  • new UglifyJsPlugin(/* ... */)
  • new webpack.optimize.ModuleConcatenationPlugin()
  • new webpack.NoEmitOnErrorsPlugin()

none模式下,不會啓動任何插件。 詳情可參考這篇文章

六、配置執行命令的腳本在package.json中如何配置

package.json文件中的script字段裏配置,以下,咱們配置3條命令,分別爲devprostart

{
  "name": "l",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev":"",   //dev命令
    "pro":"",   //pro命令
    "start":""  //start命令
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^4.35.3"
  }
}
複製代碼

配置完成後,就能夠在項目的命令行裏執行如下命令了:

npm run dev

npm run pro

npm run start
複製代碼

七、如何指定webpack-dev-server的啓動目錄

修改和webpack-dev-server有關配置的contentBase選項。

let path = require('path')
module.exports = {
    mode:'development',
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename:'bundle.js'
    },
    devServer: {
        port: 8888,//監聽的端口號
        progress: true,//啓動打包進度顯示
        contentBase: path.join(__dirname, 'dist'),//這裏指定啓動目錄
        compress: true //啓動壓縮
    }
}
複製代碼

關於webpack-dev-server的更多配置,點擊這裏
從打包的過程也能夠看出webpack-dev-server的啓動目錄,以下:

八、如何壓縮產生的html文件

使用html-webpack-plugin插件,配置以下:

plugins: [
        new HtmlWebpackPlugin({
            template: './index1.html',
            filename: 'main.html',
            minify: {
                collapseWhitespace: true,//移除空格
                removeAttributeQuotes:true//移除屬性的雙引號
            }
        })
    ]
複製代碼

官方說若是模式爲production的話,minify選項會被默認設置成true,產出的HTML文件會自動壓縮,你們能夠試試,我嘗試的不行。
minify的配置選項有不少,感興趣能夠點這裏查看更多。
其實,html-webpack-plugin能夠壓縮HTML文件,內部是依賴的是這個庫html-minifier,這個壓縮HTML的庫也能夠這樣使用:

var minify = require('html-minifier').minify;
var result = minify('<p title="blah" id="moo">foo</p>', {
  removeAttributeQuotes: true
});
result; // '<p title=blah id=moo>foo</p>'
複製代碼

關於html-minifier的更多信息,這裏

九、如何解決打包後緩存的問題

JavaScript文件解決方法:
第一種:
讓文件名稱帶有hash字符串,這樣每次打包js文件時,只有內容有變化,hash字符串就會發生變化,好比下面:

output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].[hash:8].bundle.js'
    },
複製代碼

這裏打包後的文件名稱爲main.d3a5dd20.bundle.js
第二種:
html-webpack-plugin的配置項hash置爲true,這樣打包後的js文件在插入html文件後會以?開始添加hash字符串。以下:

plugins: [
        new HtmlWebpackPlugin({
            template: './index1.html',
            filename: 'main.html',
            hash:true
        })
    ]
複製代碼

這裏打包後的文件名稱以下:

<body>
    <h1>This is a title</h1>
<script type="text/javascript" src="main.js?d3a5dd204b4d1b64170c"></script>
</body>
複製代碼

十、webpack中的loader是幹什麼的

loader的做用是對源代碼進行轉換,webpack只認識jsjson2種文件,其餘類型的文件,好比cssimgless等等,只能依靠loader去解析轉換了。
官方解釋,這裏

十一、css-loader和style-loader的做用

css-loader主要處理css文件中@importurl()語法的。官方文檔
style-loader主要做用是將css樣式以style標籤的形式插入到頁面中。官方文檔

十二、請說出loader的特色

  1. 第一個loader要返回js腳本
  2. 每一個loader只作一件事情,爲了使loader在更多場景下鏈式調用
  3. 每個loader都是一個node模塊
  4. loader有同步的,也有異步的
  5. loader有兩個執行階段,pitchnormal

官網文檔
loader的執行順序比較講究, 以下圖所示:

1三、loader有幾種寫法

配置文件寫法、行內loader寫法、命令行寫法3種

配置文件寫法:
就是將配置信息寫到webpack.config.js,寫法又有如下幾種:

13-一、直接寫loader

module.exports={
    module:{
        rules:[
            {
                test: /.js$/,
                loader: 'my-loader',
                exclude: /node_modules/
            },
        ]
    }
}
複製代碼

13-二、使用use,字符串形式

module.exports={
    module:{
        rules[
             {
                test: /.js$/,
                use: 'my-loader',//直接傳遞字符串
                exclude: /node_modules/
            },
        ]
    }
}
複製代碼

13-三、使用use,對象形式

module.exports={
    module:{
        rules[
             {
                test: /.js$/,
                use: {  //對象形式,能夠給loader傳遞參數
                    loader:'my-loader',
                    options:{}//這裏傳遞參數給loader
                }
                exclude: /node_modules/
            },
        ]
    }
}
複製代碼

13-四、使用use,數組形式

數組內的每一項能夠爲字符串,也能夠是對象。

module.exports = {
    module: {
        rules: [
            {
                test: /.js$/,
                use: [
                    'my-loader1',//字符串形式
                    { loader: 'my-loader2', options: {} }//對象形式
                ],
                exclude: /node_modules/
            },
        ]
    }
}
複製代碼

行內loader寫法:
多個loader之間用!分割。

let something=require('loader2!loader1!./profile.js')
複製代碼

行內loader可添加前綴,表明當前文件是否交由其餘loader處理:

  • -! 表示不會讓文件再去經過 pre+normal loader處理了
  • ! 表示不會讓normal loader處理了
  • !! 該文件只會讓行內loader處理
let a = require('inline-loader!./a') // !分割,inline-loader就是行內loader
let a = require('-!inline-loader!./a') // -!表示不會讓文件再去經過 pre+normal loader處理了
let a = require('!inline-loader!./a') // ! 表示不會讓normal loader處理了
let a = require('!!inline-loader!./a') // !! 該文件只會讓行內loader處理
複製代碼

命令行寫法:

webpack --module-bind jade-loader --module-bind 'css=style-loader!css-loader'
複製代碼

1四、loader的默認順序是怎樣的

從右向左,從下到上

{
    test: /\.js$/,
    use: ['loader3', 'loader2', 'loader1'] 
}
複製代碼

以上loader執行順序爲 loader1---> loader2--->loader3

{
    test: /\.js$/,
    use: {
        loader:'loader3'
    }
},
{
    test: /\.js$/,
    use: {
        loader: 'loader2'
    }
},
{
    test: /\.js$/,
    use: {
        loader: 'loader1'
    }
}
複製代碼

以上loader執行順序爲 loader1--->loader2--->loader3

1五、使用loader時如何傳遞參數

第一種:在配置文件中傳遞參數

module.exports={
    module:{
        rules[
             {
                test: /.js$/,
                use: {  //對象形式,能夠給loader傳遞參數
                    loader:'my-loader',
                    options:{}//這裏傳遞參數給loader
                }
                exclude: /node_modules/
            },
        ]
    }
}
複製代碼

第二種:行內loader傳遞參數的方法

let foo = require('expose-loader?$!./a.js')
複製代碼

1六、如何控制loader的執行順序

loader的默認順序是從右向左,從下到上,不過能夠經過enforce字段來打破這種順序。
enforce有兩個取值:pre表明第一個執行。post表明最後一個執行
有以下loader配置:

{
                test: /.js$/,
                use: 'loader1.js',
                exclude: /node_modules/,
                enforce: 'pre',//表明loader1首先被執行
            },
            {
                test: /.js$/,
                use: 'loader2.js',
                exclude: /node_modules/
            },
            {
                test: /.js$/,
                use: 'loader3.js',
                exclude: /node_modules/
            },
            {
                test: /.js$/,
                use: 'loader4.js',
                exclude: /node_modules/,
                enforce: 'post'//表明loader4最後被執行
            }
複製代碼

若是沒有配置enforce字段,執行順序爲:loader4--->loader3--->loader2--->loader1
若是配置了enforce字段,執行順序爲:loader1--->loader3--->loader2--->loader4
注意:沒有配置enforce字段的loader默認爲normal,按照默認順序執行.
若是文件在require的時候用到了行內loader的話,執行順序以下:
pre--->normal--->inline--->post

1七、如何指定loader想要處理的js文件

思考中...

1八、常見的css預處理器有哪些,對應的loader有哪些

css預處理器:lesssassstylus
對應的loaderless-loadersass-loaderstylus-loader

1九、如何實現css樣式抽離

安裝插件:

npm install mini-css-extract-plugin -D
複製代碼

配置文件:

{
     module: {
        rules: [
            {
                test: /\.css$/,
                use: [{
                    loader: MiniCssExtractPlugin.loader,//使用插件loader
                },
                    'css-loader'
                ]
            },

        ]
    },
    plugins: [
        //添加插件實例
        new MiniCssExtractPlugin({
            filename: 'index.css'
        })
    ]
}
複製代碼

mini-css-extract-plugin更多用法

20、如何實現自動添加瀏覽器前綴

安裝包:

npm install postcss-loader autoprefixer -D
複製代碼

項目根目錄下新建文件postcss.config.js,內容以下:

module.exports = {
    plugins: [require('autoprefixer')]
}
複製代碼

webpack.config.js配置:

module: {
        rules: [
            {
                test: /\.css$/,
                use: [{
                    loader: MiniCssExtractPlugin.loader,
                },
                    'css-loader',
                    'postcss-loader'//這裏加入了新的loader
                ],
                include: path.join(__dirname, './src'),
                exclude: /node_modules/
            },

        ]
    }
複製代碼

2一、抽離css後如何實現css壓縮

安裝包:

npm install optimize-css-assets-webpack-plugin -D
複製代碼

webpack.config.js配置:

{
        plugins: [
        new OptimizeCSSAssetsPlugin()
    ]
}
複製代碼

2二、壓縮js須要使用什麼插件

安裝包:

npm install uglifyjs-webpack-plugin --save-dev
複製代碼

webpack.config.js配置:

module.exports = {
  optimization: {
    minimizer: [ new UglifyJsPlugin() ],
  },
};
複製代碼

或者:

{
        plugins: [
        new UglifyJsPlugin()
    ]
}
複製代碼

uglifyjs-webpack-plugin官方文檔

2三、想把es6轉化成es5須要哪些模塊

npm install @babel/core @babel/preset-env babel-loader -D
複製代碼

2四、babel-loader和@babel/core的關係

@babel/core是核心包,提供基礎的API服務。babel-loader依賴@babel/core
關於babel的知識,推薦閱讀這兩篇:
我是第一篇
我是第二篇

2五、@babel/plugin-proposal-class-properties和@babel/plugin-proposal-decorators做用是什麼

@babel/plugin-proposal-class-properties是用來轉換class語法的。好比:
以下語法:

class Bork {
    static a = 'foo';
    static b;

    x = 'bar';
    y;
  }
複製代碼

會被轉換爲:

var Bork = function Bork() {
  babelHelpers.classCallCheck(this, Bork);
  this.x = 'bar';
  this.y = void 0;
};

Bork.a = 'foo';
Bork.b = void 0;
複製代碼

官方文檔
@babel/plugin-proposal-decorators是用來轉換修飾符(@)的。
官方文檔

2六、@babel/plugin-transform-runtime,@babel/runtime,@babel/polyfill區別是什麼

@babel/polyfill:
babel 默認只轉換 js 語法,而不轉換新的 API,好比 IteratorGeneratorSetMapsProxyReflectSymbolPromise 等全局對象,以及一些定義在全局對象上的方法(好比 Object.assign)都不會轉碼。
舉例來講,es2015Array 對象上新增了 Array.from 方法。babel 就不會轉碼這個方法。若是想讓這個方法運行,必須使用 @babel/polyfill。(內部集成了 core-js 和 regenerator) 使用時,在全部代碼運行以前增長 require('@babel/polyfill')。
@babel/plugin-transform-runtime依賴@babel/runtime,也就是說:在使用@babel/plugin-transform-runtime的時候必須把@babel/runtime當作依賴。
推薦閱讀24題的兩篇文章。

2七、如何對js代碼進行校驗

可使用eslint包對js代碼進行校驗。
安裝包:

npm i eslint eslint-loader babel-eslint -D
複製代碼

新建eslint配置文件.eslint.js:

module.exports = {
    root: true,
    //指定解析器選項
    parserOptions: {
        sourecType: 'module'
    },
    //指定腳本執行的環境
    env: {
        browser: true
    },
    //啓用的規則及其各自的錯誤級別
    rules: {
        "semi": "error",//語句強制分號結尾
        "indent": ["error",4],//縮進風格
        "quotes":["error","double"]//引號類型
    }
}
複製代碼

webpack.config.js

module: {
        rules: [
            {
                test: /\.js$/,
                use: {
                    loader: 'eslint-loader',
                    options: {
                        fix: true
                    },

                },
                include: [path.resolve(__dirname, 'src')],
                exclude: /node_modules/,
            }
        ]
    }
複製代碼

eslint官網文檔

2八、請說下暴露變量的三種方式

暴露變量其實就是暴露到全局,也就是掛在到window上。
第一種:
安裝包expose-loader

npm install expose-loader -D
複製代碼

在入口文件(webpack.config.js中的entry)中使用expose-loader,這種方式屬於內聯loader

import $ from 'expose-loader?$!jquery'
console.log(window.$)
複製代碼
  • ! --->將loader和包隔開符號
  • ? --->給expose-loader傳遞參數
  • $ --->暴露的全局變量命名爲$

固然也能夠不使用內聯loader
第一步:入口文件正常引入:

import $ from 'jquery'
console.log(window.$)
複製代碼

第二步:在webpack.config.js配置文件中配置

rules: [
            {
                test: require.resolve('jquery'),
                use: 'expose-loader?$'
            }
        ]
複製代碼

第二種:
在每一個模塊中注入$,注意是每一個模塊都有一個$,可是這個$並非全局的。
webpack.config.js配置文件中配置

plugins: [
        new webpack.ProvidePlugin({
            $: 'jquery',
            jQuery: 'jquery'
        })
    ]
複製代碼

而後模塊中能夠直接使用$
第三種:
這種方式叫引入不打包。在html文件中引入jquery,這裏以jquerycdn爲例:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
    <script src="../dist/bundle.js"></script>
</head>

<body>

</body>

</html>
複製代碼

注意:若是使用了這種方式,就不要在js文件內在引入jquery了,不然會重複打包。若是非要引入,那就修改一下webpack.config.js

module.exports = {
    //...
    externals: {
        jquery: '$'//webpack打包時,會忽略掉jquery
    },
    //...
}
複製代碼

2九、ProvidePlugin會將提供的變量掛載在window上嗎

不會,官方文檔沒有明說,可是有句話的潛臺詞代表了不會。哪句話呢,友情提示:Whenever the identifier is encountered as free variable in a module...
參考官方文檔

30、如何在webpack中處理圖片模塊

先說一下,使用圖片的幾種方式。

  • js中建立圖片來引入
  • 在css中引入
  • 在html文件中用img標籤引入

30-一、js中建立圖片來引入

使用方式:
安裝包file-loader

npm install file-loader -D
複製代碼

js文件內:

import logo from '../logo.png'

//file-loader 默認會在內部生成一張圖片到build目錄下,被導入的圖片在js文件內是一個hash字符串
console.log(logo)//19470b4db4deed52a8ba081c816e8f0d.png

let image = new Image()

image.src = logo
複製代碼

webpack.config.js文件內配上相應的loader

module: {
        rules: [
            {
                test: /\.(png|jpg|gif)$/,
                loader: 'file-loader'
            }
        ]
    },
複製代碼

30-二、在css中引入

使用方式:
安裝包style-loadercss-loader,固然還能夠安裝css預處理包

npm install style-loader css-loader -D
複製代碼

css文件內:

body{
    background: url("../logo.png")
}
複製代碼

webpack.config.js文件內配上相應的loader

module: {
        rules: [
            {
                test: /\.css$/,
                loader: 'style-loader!css-loader'
            }
        ]
    },
複製代碼

30-三、在html文件中用img標籤引入

使用方式:
安裝包html-withimg-loader

npm install html-withimg-loader -D
複製代碼

html文件內:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="../dist/bundle.js"></script>
</head>

<body>
    <img src="./logo.png" alt="">
</body>

</html>
複製代碼

webpack.config.js文件內配上相應的loader

module: {
        rules: [
            {
                test: /\.html$/,
                loader: 'html-withimg-loader'
            },
        ]
    },

複製代碼

3一、webpack打包時如何處理html的img

使用html-withimg-loader包來處理html的img。

3二、webpack打包時如何把圖片變爲base64

安裝包url-loader.

npm install url-loader -D
複製代碼

webpack.config.js文件內配上相應的loader

module: {
        rules: [
            {
                test: /\.(png|jpg|gif)$/,
                use: {
                    loader: 'url-loader',
                    options: {
                        limit: 200 * 1024
                    }
                }
            },
        ]
    },

複製代碼

當圖片小於設定的大小(K)的時候,用base64來轉化,不然用file-loader產生真實的圖片。

3三、file-loader和url-loader的關係

他倆其實沒有必然聯繫,只能說能夠搭配起來一塊兒工做。url-loader只能將圖片解析成base64,當圖片大小超過了限制,url-loader就會把解析圖片的工做交給其餘工具(默認是file-loader),固然,當圖片大小超過了限制,而咱們想用其餘工具來處理圖片,能夠經過參數來控制:

module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpg|gif)$/i,
        use: [
          {
            loader: 'url-loader',
            options: {
              fallback: 'responsive-loader',//當圖片大小超過8K,用responsive-loader處理
              limit: 8*1024,
            },
          },
        ],
      },
    ],
  },
};
複製代碼

能夠參考篇幅不長的官方文檔

3四、publicPath的做用是什麼

publicPath指定的路徑會被做爲前綴添加到全部的url上。這裏的url指的是:

  • html文件中的link標籤,script標籤、img標籤
  • css中的帶有文件引入的屬性等。好比:background:url()

通常當靜態資源放在CDN時,publicPath會指定CDN的路徑。
官方文檔

3五、如何配置多頁面應用

配置多頁面就須要配置webpack的多入口。

module.exports = {
    mode: 'development',
    entry: {
        main: './src/main.js',//入口1,main.js
        index: './src/index.js',//入口2,index.js
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].[hash:8].bundle.js'
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: './index.html',
            chunks: 'main',//這裏的main和entry裏的main屬性須要保持一致
            filename: 'index.html'
        }),
        new HtmlWebpackPlugin({
            template: './index.html',
            chunks: 'index',//這裏的index和entry裏的index屬性須要保持一致
            filename: 'main.html'
        })
    ]
}

複製代碼

官方文檔

3六、如何在html文件指定引入某個js文件

思考中...

3七、source-mapeval-source-map的區別

這個配置主要是debug用的,配置選項有不少,這裏挑選4個說明。

  • source-map 生成map文件,定位到行列
  • eval-source-map 不生成map文件,定位到行列
  • cheap-module-source-map 生成map文件,定位到行
  • cheap-module-eval-source-map 不生成map文件,定位到行

官方文檔

3八、如何實時編譯打包處的文件

在webpack配置文件中新增以下配置信息:

module.exports = {
    mode: 'development',
     //開啓實時編譯
    watch: true,
     //實時編譯的配置選項
    watchOptions: {
        ignored: /node_modules/,
        poll:1000,//每秒詢問文件變動的次數
        aggregateTimeout:500//防止重複保存頻繁從新編譯,500毫秒內重複保存不打包
    }
}
複製代碼

3九、aggregateTimeout:500的做用是什麼

當檢測文件再也不發生變化,會先緩存起來,等待一段時間後,再通知監聽者,這個等待時間經過aggregateTimeout配置。

40、bannerPlugin的做用

bannerPlugin的做用是在產出的資源文件頭部添加信息,好比:添加做者、版本號等信息。

let webpack=require('webpack')
module.exports={
    //...
    plugins: [
        new webpack.BannerPlugin('author:wangZhi')
    ],
    //...
}
複製代碼

產出的文件頭部以下所示:
css

/*! author:wangZhi */
body{
    background:red;
}
複製代碼

js

/*! author:wangZhi */
/******/ (function(modules) { // webpackBootstrap
/******/ 	// The module cache
/******/ 	var installedModules = {};
/******/
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
複製代碼

4一、webpack如何配置代理

首先啓動一個web服務,配置以下:

var path = require('path');

module.exports = {
  //...
  devServer: {
    contentBase: path.join(__dirname, 'dist'),
    compress: true,
    port: 9000
  }
};
複製代碼

而後配置代理:

proxy: {
            '/api': {//以/api開頭的請求會被代理到'https://other-server.example.com'
                target: 'https://other-server.example.com',
                pathRewrite: {
                    '^/api': ''//以/api開頭的路徑會被替換成''
                }
            }
        }
複製代碼

官方文檔

4二、before函數中的app有什麼做用

webpack-dev-server是依靠express啓動一個web服務的,配置中的app就是express中的app,有了app,咱們能夠編寫接口,響應接口等,至關於前臺本身mock一些數據。建議看看express官網

module.exports = {
  //...
  devServer: {
    before: function(app, server) {
        //編寫一個/some/path接口,後續請求會在這裏直接處理
      app.get('/some/path', function(req, res) {
        res.json({ custom: 'response' });
      });
    }
  }
};
複製代碼

4三、webpack-dev-middleware做用是什麼

服務端啓動webpack,webpack-dev-middleware實際上是一個express的中間件。

let webpack = require('webpack')
let express = require('express')
let config = require('./webpack.config')
let middle = require('webpack-dev-middleware')
let app = express();
//webpack提供的方法,傳入webpack配置,獲得一個編譯對象 
let compiler = webpack(config);
//使用中間件
app.use(middle(compiler))
//監聽端口
app.listen(2000)
複製代碼

官方文檔

4四、resolve屬性有哪些配置

列出幾個經常使用的配置:

module.exports = {
  //...
  resolve: {
        //模塊查找路徑
        modules: [path.resolve('node_modules'),'mydir'],
        //配置別名
        alias: {
            bootstrap:'bootstrap/dist/css/bootstrap.css'
        },
        //查找字段
        mainFields: ['main', 'style'],
        //查找文件名
        mainFiles: ['index.js'],
        //查找文件的後綴名
        extensions:['.js','.css','.vue']
    },
};
複製代碼

官方文檔

4五、如何定義別名

見上44題。

4六、如何省略引入文件的後綴

見上44題。

4七、如何定義環境變量

定義環境變量須要用到webpack的一個包--->definePlugin

module.exports = {
    //...
    plugins: [
        new webpack.DefinePlugin({
            DEV: 'dev',
            DEV_str: JSON.stringfiy('dev'),
            FLAG: 'true',
            FLAG_str: "'true'",
            expression: '1+1',
            expression_str: JSON.stringify('1+1')
        })
    ]
}
複製代碼

上述配置定義了6個環境變量,打包編譯後的結果爲:

  • DEV--->報錯,dev is not defined
  • DEV_str---> 'dev'(字符串)
  • FLAG---> true(布爾值)
  • FLAG_str---> 'true'(字符串)
  • expression---> 2(數字型)
  • expression_str---> '1+1'(字符串)

4八、如何區分開發環境和生產環境

第一種方式,能夠經過設置環境變量來區分,設置環境變量見上一題。

if(DEV==='development'){
    //do something
}else if(DEV==='production'){
    //do something
}
複製代碼

第二種方式,建立多套配置文件。
安裝包 webpack-merge

npm install --save-dev webpack-merge
複製代碼

項目目錄:

webpack-demo
  |- package.json
  |- webpack.common.js //開發環境、生產環境公用配置文件
  |- webpack.dev.js //開發環境配置文件 
  |- webpack.prod.js //生產環境配置文件
  |- /dist
  |- /src
    |- index.js
    |- math.js
  |- /node_modules
複製代碼

webpack.common.js文件:

const path = require('path');
  const { CleanWebpackPlugin } = require('clean-webpack-plugin');
  const HtmlWebpackPlugin = require('html-webpack-plugin');
 
  module.exports = {
    entry: {
      app: './src/index.js'
    },
    plugins: [
      new CleanWebpackPlugin(),
      new HtmlWebpackPlugin({
        title: 'Production'
      })
    ],
    output: {
      filename: '[name].bundle.js',
      path: path.resolve(__dirname, 'dist')
    }
  };
複製代碼

webpack.dev.js文件:

const merge = require('webpack-merge');
  const common = require('./webpack.common.js');
 
  module.exports = merge(common, {
    mode: 'development',
    devtool: 'inline-source-map',
    devServer: {
      contentBase: './dist'
    }
  });
複製代碼

webpack.prod.js文件:

const merge = require('webpack-merge');
  const common = require('./webpack.common.js');
 
  module.exports = merge(common, {
    mode: 'production',
  });
複製代碼

參考官方文檔

4九、webpack-merge做用是什麼

webpack-merge的做用是合併對象。
最基本的用法:

let merge = require('webpack-merge')
let newObj = merge(obj1,obj2,obj3,...)
複製代碼

仍是看文檔

50、如何不解析某些依賴庫

webpack的配置文件中,經過配置externals字段能夠達到不解析某些依賴庫的目的。以下:

module.exports = {
    //...
    externals: {
        jquery: '$'//webpack打包時,會忽略掉jquery
    },
    //...
}
複製代碼

官方文檔

5一、如何設置loader的解析文件夾

一、直接寫絕對路徑

{
    test: /.js$/,
    use: path.resolve(__dirname,'loader/loader1.js')
}
複製代碼

二、配置別名

resolveLoader: {
        alias: {
            loader1: path.resolve(__dirname, 'loader', 'loader1')
        }
    }
複製代碼

三、配置modules

resolveLoader: {
        modules: ['node_modules', path.resolve(__dirname, 'loader')]
    }
複製代碼

我是官方文檔

5二、使用了moment後默認會引入locale文件夾

思考中...

5三、dllPlugin如何使用

打包一個dll文件:

let path = require('path');
//引入插件,webpack內置插件
let DllPlugin = require('webpack/lib/DllPlugin')
module.exports = {
    mode: 'development',
    entry: {
        //將react、react-dom庫打包成動態連接庫
        react:['react','react-dom']
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].dll.js',
        //動態連接庫導出的全局變量
        library: '_dll_[name]'
    },
    plugins: [
        new DllPlugin({
            //name需和output.library一致。
            name: '_dll_[name]',
            //生成的json文件存放目錄
            path: path.join(__dirname, 'dist', '[name].manifest.json')
        })
    ]
}
複製代碼

按照如上配置,咱們最終獲得了一個react.dll.js文件,該文件內容通過刪減替換整理後,以下:

var _dll_react = (function (modules) {
    var installedModules = {};
    function __webpack_require__(moduleId) {
        if (installedModules[moduleId]) {
            return installedModules[moduleId].exports;
        }
        var module = installedModules[moduleId] = {
            i: moduleId,
            l: false,
            exports: {}

        };
        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
        return module.exports;

    }
    return __webpack_require__(0);
})({
    "module1": (function (module, exports, __webpack_require__) { /* ... */}),
    "module2": (function (module, exports, __webpack_require__) { /* ... */}),
    "module3": (function (module, exports, __webpack_require__) { /* ... */}),
    "module4": (function (module, exports, __webpack_require__) { /* ... */}),
    0: (function (module, exports, __webpack_require__) {
        eval("module.exports = __webpack_require__")
    })
})
複製代碼

爲了直觀,我將源碼中的相似./node_modules/object-assign/index.js這樣的屬性名替換成了module1,module2等等。
分析上述代碼,咱們能夠得知,_dll_react變量其實就是__webpack_require__方法。該方法接受一個模塊id,返回該模塊的內容。
再來看一下,生成的react.manifest.json文件,內容通過刪減整理以下:

{
    "name": "_dll_react",
    "content": {
        "./node_modules/react-dom/index.js": {
            "id": "./node_modules/react-dom/index.js",
            "buildMeta": {
                "providedExports": true
            }
        }
        //...
    }
}
複製代碼

content對象裏的鍵名./node_modules/react-dom/index.js就是模塊請求路徑,也就是說,當webpack遇到了以下語句require('./node_modules/react-dom/index.js')時,webpack會拿着調用require方法傳入的路徑去react.manifest.json文件內的content對象中找到鍵爲該路徑的屬性,而後webpack就獲取到了該路徑對應的模塊內容。
若是咱們不使用動態連接庫,當webpack遇到了以下語句require('./node_modules/react-dom/index.js')時,webpack會拿着調用require方法傳入的路徑去獲取文件內容,而後拼接頭部信息(就是function (module, exports, __webpack_require__) {}),而後遞歸解析文件內容的require語句,而後將依賴寫入依賴列表。
綜上所述,使用動態連接庫確實在打包速度上獲得了必定的提高。
使用一個dll文件相對來講就比較簡單了,按照格式寫就能夠了:

let path = require('path');
//引入插件,webpack內置插件
let DllReferencePlugin = require('webpack/lib/DllReferencePlugin')
module.exports = {
    mode: 'development',
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].bundle.js',
    },
    plugins: [
        //使用動態連接庫
        new DllReferencePlugin({
           manifest:require('./dist/react.manifest.json')
        })
    ]
}
複製代碼

官方文檔

5四、如何實現多線程打包,優化前5個

安裝包happypack

npm install --save-dev happypack
複製代碼

webpack.config.js配置文件以下:

module.exports = {
    rules: [
        {
            test: /\.js$/,
            use: 'happypack/loader?id=jsx'
        },

        {
            test: /\.less$/,
            use: 'happypack/loader?id=styles'
        },
    ],
    plugins: [
        new HappyPack({
            id: 'jsx',
            threads: 4,
            loaders: ['babel-loader']
        }),

        new HappyPack({
            id: 'styles',
            threads: 2,
            loaders: ['style-loader', 'css-loader', 'less-loader']
        })
    ]
}
複製代碼

happypack如何工做的呢,一圖勝千言:

官方文檔

5五、tree-shaking是否支持require語法和有什麼做用

不支持require語法,依賴於ES2015模塊的靜態結構,好比:import export。 做用就是可以去除未用到的代碼。
官方文檔

5六、scope hosting的做用是什麼

Scope Hoisting 可讓 Webpack 打包出來的代碼文件更小、運行的更快, 它又譯做 "做用域提高",是在 Webpack3 中新推出的功能。

  • 代碼體積更小,由於函數聲明語句會產生大量代碼
  • 代碼在運行時由於建立的函數做用域更少了,內存開銷也隨之變小

在配置文件中添加一個新的插件,就能夠實現scope hosting功能。

module.exports = {
  plugins: [
    new webpack.optimize.ModuleConcatenationPlugin()
  ]
}
複製代碼

參考文章

5七、請簡述webpack整個工做流程

webpack的打包過程比較複雜,這裏用一張圖簡述一下,權當拋磚引玉了。

5八、請簡述webpack中的HotModuleReplacement原理。

一、當文件發生變化後,webpack會從新打包,打包完成後,發佈done事件。
二、done回調函數執行,經過服務端與客戶端創建的長鏈接發送hash值到客戶端。
三、客戶端收到hash值以後,確認是否要更新。若是更新,則會經過Ajax去請求manifest.json文件,該文件記錄了全部發生變更的模塊。
四、經過manifest.json文件,客戶端使用jsonp方式去拉取每個變更模塊的最新代碼。
五、客戶端更新模塊,加入了3個屬性:parentschildrenhot
六、經過模塊id找到父模塊中全部依賴該模塊的回調函數並執行。
七、頁面自動更新,熱替換完成。
以下圖所示:

5九、webpack代碼分割方式有哪些,其中import()方法的原理是什麼。

第一種:多入口

module.exports = {
    entry: {
        page1: './src/page1.js',
        page2: './src/page2.js',
        page3: './src/page3.js'
    }
}
複製代碼

第二種:webpack內部配置項。

module.exports = {
    optimization:{
        splitChunks:{
            cacheGroups:{
                vendors:{
                    chunks:'initial',//指定分割的類型,默認3種選項 all async initial
                    name:'vendors',//給分割出去的代碼塊起一個名字叫vendors
                    test:/node_modules/,//若是模塊ID匹配這個正則的話,就會添加一vendors代碼塊
                    priority:-10 //優先級
                },
                commons:{
                    chunks:'initial',
                    name:'commons',
                    minSize:0,//若是模塊的大小大於多少的話才須要提取
                    minChunks:2,//最少最幾個chunk引用才須要提取
                    priority:-20
                }
            }
        }
    }
}
複製代碼

第三種:調用webpack提供的import方法。
假設,項目的入口文件是index.js,內容以下:

let button = document.createElement('button');
    button.innerHTML = '點我點我';
    button.addEventListener('click',event=>{
        debugger;
        import('./hello.js').then(result=>{
            alert(result.default);
        });
    });
    document.body.appendChild(button);
複製代碼

入口文件index.js被編譯後的代碼以下:

let button = document.createElement('button');
button.innerHTML = '點我點我';
button.addEventListener('click', event => {
    __webpack_require__.e("src_hello_js.js").then(__webpack_require__.t.bind(__webpack_require__, "./src/hello.js")).then(result => {
        alert(result.default);
    });
});
document.body.appendChild(button);
複製代碼

看重點代碼import('./hello.js')被編譯成了__webpack_require__.e("src_hello_js.js").then(__webpack_require__.t.bind(__webpack_require__, "./src/hello.js")),./hello.js會被看成爲一個入口文件,而後打包成一個獨立的文件,和項目入口文件index.js打包出來的文件放在一塊兒。也就是說,咱們這裏的項目原本就一個入口文件,結果打包出來兩個文件,緣由就是使用了import方法將代碼進行了分割。
__webpack_require__.e方法代碼以下:

__webpack_require__.e = function (chunkId) {
    return new Promise((resovle, reject) => {
        installedChunks[chunkId] = resovle;
        let script = document.createElement('script');
        script.src = chunkId;
        document.body.appendChild(script);
    }).catch(error => {
        alert('異步加載失敗');
    });
}
複製代碼

能夠看到該方法主要功能就是根據傳入的chunkId使用jsonp去拉取對應的模塊代碼。這裏它返回了一個promise,並將resolve放到了全局installedChunks對象上。由於這裏不能肯定jsonp何時成功,因此沒法調用resolve,只能將它掛載到全局變量中。那何時能肯定jsonp成功了呢,答案是在jsonp的回調函數裏能夠肯定,也就是下面的webpackJsonp方法裏。
先來看看jsonp拉取回來的代碼長什麼樣子吧,以下:

window.webpackJsonp("src_hello_js.js", {
    "./src/hello.js": (function (module, exports, __webpack_require__) {
        module.exports = 'hello';
    }),
});
複製代碼

jsonp標準格式,返回一個方法調用,參數就是響應的數據。
window.webpackJsonp方法代碼以下:

window.webpackJsonp = (chunkId, moreModules) => {
    for (moduleId in moreModules) {
        modules[moduleId] = moreModules[moduleId];
    }
    installedChunks[chunkId]();//resolve()
    installedChunks[chunkId] = 0;
}
複製代碼

webpackJsonp主要工做就是將拉取回來的模塊一一掛載到全局的modules對象中。而且改變對應的promise狀態,也便是執行事先放在全局變量中的resolve方法,這樣就會執行以後的then方法了。
__webpack_require__.t方法代碼以下:

__webpack_require__.t = function (value) {
    value = __webpack_require__(value);
    return {
        default:
            value
    };
}
複製代碼

該方法就是簡單的去加載模塊,而後返回加載後的結果。
一圖勝千言:

相關文章
相關標籤/搜索