顯微鏡下的webpack4入門

前端的構建打包工具不少,好比grunt,gulp。相信這二者你們應該是耳熟能詳的,上手相對簡單,並且所需手敲的代碼都是比較簡單的。而後webpack的出現,讓這二者打包工具都有點失寵了。webpack比起前二者打包工具,對於前端程序員JS編程能力的要求仍是挺高的。不過須要兼容ie8及如下的小夥伴們,就不要考慮webpack了,他很傲嬌地不兼容!css

webpack 前期準備

webpack,這是一個組合詞「web」+「pack」,web就是網站的意思,「pack」有打包的意思,webpack組合在一塊兒就是網站打包的意思,這個名字至關暴力簡單明瞭啊。webpack這款工具雖然很難學,可是自由度很大,玩轉以後有種爲所欲爲的感受。html

在學習webpack以前,有幾個基礎的概念:前端

  • JavaScript,若是這個編程能力不過關,好比不清楚ES6的語法,那麼webpack學起來有些費力,仍是要先去學習基礎知識。
  • nodejs,關於nodejs的平常用法,仍是須要了解的,否則webpack改如何啓動,都無從下手。
  • CommonJS,這個規範是須要學習下的,webpack的配置文件就是按照這個規則。

若是以上幾個技能都具有,那麼恭喜咱們能夠開始webpack的學(求)習(虐)之旅了。node

webpack 打包原理

在使用webpack以前,咱們須要瞭解webpack的工做原理。webpack打包出來的JS不只僅是壓縮混淆咱們的源文件,並且還對它作了其餘的處理。webpack

下面是webpack打包出來的JS文件和源文件:git

  • "./src/index.js"源文件
let str="index"
console.log(str)
複製代碼
  • webpack打包後
(function(modules) { // webpackBootstrap
    /*此處省略N+1行*/
    return __webpack_require__(__webpack_require__.s = "./src/index.js");
})
({
    "./src/index.js":(function(module, exports) {
        eval("let str=\"index\"\r\nconsole.log(str)\n\n//# sourceURL=webpack:///./src/index.js?");
    })
});
複製代碼

是否是感受原本小巧的JS,一會兒變得臃腫了??彷佛用webpack沒有意義啊!不只不能忙我壓縮文件,還把源文件變胖了。程序員

不要急,咱們再看一個例子:github

  • "./src/index.js"源文件
require("./page1.js")
let str="index"
console.log(str)
複製代碼
  • "./src/page1.js"源文件
let str="page1"
console.log(str)
複製代碼
  • webpack打包後
(function(modules) { // webpackBootstrap
    /*此處省略N+1行*/
    return __webpack_require__(__webpack_require__.s = "./src/index.js");
})
({
    "./src/index.js": (function(module, exports, __webpack_require__) {
        eval("__webpack_require__(/*! ./page1.js */ \"./src/page1.js\")\r\nlet str=\"index\"\r\nconsole.log(str)\n\n//# sourceURL=webpack:///./src/index.js?");
    }),
    "./src/page1.js":(function(module, exports) {
        eval("let str=\"page1\"\r\nconsole.log(str)\n\n//# sourceURL=webpack:///./src/page1.js?");
    })
});
複製代碼

當有模塊導入的時候,這個胖JS就展示了他真正的實力。經過__webpack_require__來實現JS之間導入的功能。至關於咱們再也不須要用requirejs,seajs此類包管理器管理咱們的前端模塊了。webpack幫助咱們完成了此類工做。是否是忽然以爲這個胖JS不胖了。web

webpack的打包原理,就是將各個模塊變成字符串,存入健值或者數組之中,而後每一個模塊之間的關係,經過__webpack_require__這個方法來實現。最後經過eval這個函數將字符串變成可執行代碼。typescript

若是你們對__webpack_require__的實現原理感興趣,能夠本身打包一個文件,不要壓縮混淆,而後研究研究。

對webpack的期許

webpack這個工具,不可能只有打包壓縮這個功能吧。既然是前端工具,那麼必然要具有如下功能:

  • 代碼處理,如打包,編譯等
  • 自動生成HTML文件,好比模板生成頁面
  • 本地服務器,這個是必備功能,否則沒法調試頁面
  • 自動編譯代碼,刷新瀏覽器,這個你們喜歡稱之爲hot replacement(熱替換,熱更新),也就是(修改過的)部分更新

那咱們逐步來了解下webpack這些功能該如何實現。

webpack從0開始

若是你以前並未使用過webpack,那麼就須要安裝一下webpack,順便學習下如何啓動webpack。

STEP 1 INSTALL

webpack從4開始,webpack分紅了兩個包一個webpack一個webpack-cli,因此安裝的時候要安裝兩個包,以及這個包咱們是工具,非網站所依賴的包,因此記得放在開發依賴包之中。

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

也許咱們想能夠直接安裝webpack,不要webpack-cli。可是現實很殘酷,若是沒有安裝CLI,系統就會告訴你,cli是必不可少的,否則webpack就罷工了。

One CLI for webpack must be installed.

STEP2 RUN

安裝好了以後,咱們應該怎麼運行呢?這裏有兩個途徑:

  • npm v8.5以上有一個操做叫作npx,這個是幹嗎的呢,是幫忙咱們直接執行.bin,目錄下的文件。node_modules\.bin\webpack.cmd在這個路徑下有webpack的執行命令,咱們能夠打開看看。當咱們npx webpack的時候,就是運行了這個文件。
  • 經過配置package.json來運行文件,有個字段叫作scripts,咱們加一個start,而後後面跟上命令。到時候咱們呼喚npm start就要能夠運行webpack了。
"scripts": {
    "start": "webpack --config webpack.config.js"
 }
複製代碼

webpack4開始支持零配置,也就是說我不用寫webpack.config.js也能夠運行。那咱們就運行試試,結果出現了一個警告:

WARNING in configuration The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment. You can also set it to 'none' to disable any default behavior. Learn more: webpack.js.org/concepts/mo…

這個警告就是告訴咱們,webpack4中的mode參數默認是production,因此若是是development的狀況就必定要配置了。感受是零配置彷佛是很是牛逼的一個操做,可是實際上仍是須要手動配置的,由於這個零配置只是幫咱們作掉了一些簡單的事,好比線上就壓縮JS,開發版就不壓縮JS,還有一些默認的路徑之類的。實際上開發的時候,默認的路徑確定是不夠用的。咱們仍是老老實實寫配置吧。

咱們配置一下,而且運行一下,在開發環境下打包,生成了一個/dist/main.js文件。奇怪個人html文件怎麼沒有打包過來?對,HTML文件須要咱們本身在dist之中建立的,也就是/dist/index.html。而且路徑要寫好即將生成的JS連接。好比/dist/main.js在html中引入,我就須要寫成<script src="./main.js"></script>

module.exports = {
    mode:"development",
};
複製代碼

這個配置文件,你們都沒有以爲寫法很熟悉?對!就是CommonJs規範!下一節會詳細解釋webpack.config.js該如何配置。

webpack的心臟——webpack.config.js

webpack的一切操做都配置在webpack.config.js之中,能夠說配好webpack.config.js,咱們就能夠坐等新鮮出爐的網站了。

從官方文檔來看,webpack一共有5個主要地配置參數

  • Entry:切入點,也就是JS程序入口,按照這個入口開始建立模塊依賴,默認./src/index.js
  • Output:輸出口,打包程序的安放位置,默認./dist/main.js
  • Loaders:加載器,將除了JS和JSON之外的文件解析加載,好比txt,css等等。
  • Plugins:插件,能夠作一些更加牛逼的效果,通常要new一個插件對象。
  • Mode(新增):productiondevelopment,這個是webpack4新增的一個屬性,用於區分開發版與線上版,也是很貼心的設置了。

Entry&Output,以及chunk的概念

在學些webpack的配置以前,咱們最早接觸的就是輸入Entry和輸出Output的配置。這裏須要引入一個chunk的概念,咱們在配置Entry的時候,有時候會設置好多個入口,這每個入口都是一個chunk,也就是說chunk是根據Entry的配置而來的。你們必定要區分清楚chunk和module的概念啊。module就是編程的時候,咱們所寫的一塊一塊的功能塊,而後模塊之間會有依賴。而後chunk只是將當前模塊和他的依賴模塊,一塊兒打包起來的代碼塊。

配置Entry,切入點JS入口也不是件容易的事。

Entry配置

  • 單一入口,單個文件。整個程序只有一個JS,這個配置就很簡單了,我麼也能夠不配置,由於默認./src/index.js。單個文件之間傳入字符串便可。
entry: '須要打包的JS的相對或者絕對地址'
複製代碼
  • 單一入口,多個文件。有時候咱們有好多獨立的JS文件,可是我只想導出一個JS,這個時候就須要傳入數組了。
entry: ["待打包JS-1","待打包JS-2"]
複製代碼
  • 多個入口,單個文件。這個時候咱們就要配置健值了,都是默認值,怎麼識別誰是誰。通常來講一個HTML只須要一個chunk,也就是一個入口點。因此這個通常用於多張頁面的配置。
entry: {
    JS1: "待打包JS-1",
    JS2: "待打包JS-2"
}
複製代碼
  • 多個入口,多個文件。前面提到一個HTML只須要一個入口點,因此這裏咱們能夠借鑑數組來完成此操做。
entry: {
    JS1: ["待打包JS1-1","待打包JS1-2"],
    JS2: ["待打包JS2-1","待打包JS2-2"]
}
複製代碼

Output配置

輸出口,安放打包好的JS,不配置就打包到默認文件,默認./dist/main.js

若是不須要分入口點,整個網站用一個JS。那麼配置一個文件名就能夠了。

output: {
    filename: 'bundle.js',
}
複製代碼

須要指定文件夾的操做,就再加一個path字段便可。

output: {
    filename: 'bundle.js',
    path: __dirname + '/dist'
}
複製代碼

然而現實中,咱們不可能只有一個JS,因此這個時候咱們就須要配置多個輸出口,不過這個不像entry能夠配置健值。可是有一個很簡便的辦法filename: '[name].js',文件名咱們用[name],這樣打包出來的Js文件就會按照Entry配置的chunk名,來命名了。

固然咱們常常回碰到CDN的問題,一個JS會被緩住,這時候咱們能夠用[hash]這個參數,來幫咱們filename: '[name].[hash].js'這樣每次生成的JS名就不同了。

LOADER,模塊的概念

在webpack中,任何文件均可以變成一個模塊,而後被打包到JS之中。可是JS只認識JS,像CSS,或者typescript這類的非標準JS,該如何處理?這個時候Loader就出現了,他幫助webpack將CSS此類文件變成JS可識別的內容而後打包。全部的loader都須要額外下載安裝,這裏以最經常使用的CSS爲例子,看咱們如何將CSS打包到JS之中。

  • 安裝css-loader這個加載器
npm install --save-dev css-loader
複製代碼

關於css-loader的用法,你們能夠參考下官網

  • 在webpack中配置。你們不要把loader的配置名寫成了loader,他的在webpack中的配置名是module.rule
module: {
    rules: [
        {
          test: /\.css$/,
          use: [
            { loader: 'style-loader'},
            { loader: 'css-loader',options: {modules: true}}
          ]
        }
      ]
}
複製代碼
  • 添加style-loader

也就是說loader全部的配置都在rules之下。這裏我還配置了style-loader,那麼咱們既然又了css-loader爲何還要style-loader呢?感受很累贅啊。那麼接下來就要說說這兩個loader的不一樣了。

打開styleloader的官網,咱們能夠發現:

Adds CSS to the DOM by injecting a <style> tag

也就是說style-loader就幹一件事就是將咱們處理好的CSS插入到DOM之中,不然咱們的CSS只編譯不生效。

若是咱們不喜歡內聯樣式,而且以爲CSS文件不必編譯到JS文件之中,那麼咱們能夠直接引入一個文件。咱們能夠這樣配置。

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

利用style-loader/urlfile-loader來加載文件。這個時候會在咱們的生產文件夾下新建一個css文件,而後js中會加載這個新建的css文件的路徑。咱們無需在頁面上配置link,js會幫助咱們自動生成一個link,引入咱們的css文件。這樣咱們就不用將css和js打包到一塊兒啦。

PLUGINS,更多優化操做

若是說loader只是對於JS的一個操做,好比將CSS轉化到JS之中啦,那麼plugins的功能就更加普遍,並不侷限加載編譯JS,好比HTML文件的操做。

這裏有一個我剛開始的遇到的問題,就是:

webpack主要是負責JS的編譯管理,那麼個人HTML文件呢?難道要我一個個在dist之中建立好嗎??

這個時候HTML Webpack Plugin出現啦,這個插件是專門用於建立管理HTML的。

首先是安裝npm i --save-dev html-webpack-plugin,而後是配置webpack:

const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
    mode:"development",
    plugins: [
        new HtmlWebpackPlugin()
    ]
}
複製代碼

通常插件都是建立一個新的實例,而後加入plugins這個數組之中。

而後咱們來看看這個HtmlWebpackPlugin插件,這個插件很強大,咱們不只能夠控制模版,還能夠配置頁面內容,像下方這樣。

test.html

<body>
    <%= htmlWebpackPlugin.options.title %>
</body>
複製代碼

webpack.config.js

plugins: [
    new HtmlWebpackPlugin(), //生成自動的index.html
    new HtmlWebpackPlugin({  // 生成一個test.html
        title: 'Custom template using Handlebars',
        filename: 'test.html',
        template: path.join(__dirname,'src/test.html')
    })
],
複製代碼

由上述例子能夠看出,爲了保證插件的靈活性,好比我每一個頁面的配置不同,咱們就能夠new好幾個插件來處理咱們的html文件。一個實例處理一個頁面。

webpack4.0的新特點——mode

MODE有三個參數productiondevelopmentnone,前兩個是有預設的插件,而最後一個則是什麼都沒有,也就是說設置爲none的話,webpack就是最初的樣子,無任何預設,須要從無到有開始配置。

咱們來研究下他們之間的配置的區別,首先是二者都有的一個new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("development|production") }),這個是用來讓咱們能夠直接在js中引用"process.env.NODE_ENV"的值,這樣就能夠在JS之中經過這個值來區別開發板與先上版本的不一樣腳本。

編譯以前的index.js

console.log(process.env.NODE_ENV)
複製代碼

編譯以後的index.js

console.log("development")
複製代碼

咱們能夠看到直接將咱們的process.env.NODE_ENV替換成了因此定義的內容。這個小功能能夠幫助咱們在寫業務JS的時候,區分線上版本與開發版本。

development

咱們接着看看其餘的開發中使用的插件NamedModulesPluginNamedChunksPlugin,本來咱們的webpack並不會給打包的模塊加上姓名,通常都是按照序號來,從0開始,而後加載第幾個模塊。這個對機器來講無所謂,查找載入很快,可是對於人腦來講就是災難了,因此這個時候給各個module和chunk加上姓名,便於開發的時候查找。

在沒有mode的狀況下,這些插件須要本身配置,而有了mode以後,咱們的配置就能夠省略了。

// webpack.development.config.js
module.exports = {
+ mode: 'development'
- devtool: 'eval',
- plugins: [
-   new webpack.NamedModulesPlugin(),
-   new webpack.NamedChunksPlugin(),
-   new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("development") }),
- ]
}
複製代碼

production

在線上版本中,咱們第一個須要處理的就要混淆&壓縮JS了吧。在線上mode中,自帶JS混淆壓縮,能夠說這個功能很方便了。

// webpack.production.config.js
module.exports = {
+  mode: 'production',
-  plugins: [
-    new UglifyJsPlugin(/* ... */),
-    new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("production") }),
-    new webpack.optimize.ModuleConcatenationPlugin(),
-    new webpack.NoEmitOnErrorsPlugin()
-  ]
}
複製代碼

本地服務器&&hot refresh

官方文檔一共給出了3中實時編譯的方法:--watch,webpack-dev-server``和webpack-dev-middleware`

--watch webpack-dev-server webpack-dev-middleware
實時編譯 yes yes yes
服務器 no yes yes
hot no yes yes
代碼上手 簡單 中等 稍困難

--watch

--watch是個好方法,運行以後,會自動給咱們編譯文件。可是瀏覽器須要手動刷新才能出現最新的內容。

webpack-dev-server

webpack-dev-server雖然,能夠直接在config中配置參數,可是仍是須要安裝一下,纔可使用。

npm install --save-dev webpack-dev-server

webpack不產出任何編譯後的文件。他只提供內存中的代碼,僞裝是真是的代碼。若是你但願在其餘目錄中讀取文件。能夠更換publicPath 選項。每次修改都會實時編譯。

可是使用webpack-dev-server,修改文件,並不會實時刷新瀏覽器,咱們須要一些配置才能夠。

首先須要在pligns中加入new webpack.HotModuleReplacementPlugin()

plugins: [
        new webpack.HotModuleReplacementPlugin()
    ],
    devServer: {
        contentBase: path.join(__dirname, 'dist'),
        publicPath:"/",
    },
複製代碼

webpack-dev-server雖然很方便,配置也簡單,可是他編譯出來的文件與npx webpack編譯出來的並不同,所以調試起來未必很方便。

webpack-dev-middleware

看見middleware就應該知道這個是一箇中間件,用於連接webpack的編譯功能和其餘nodejs服務器框架的橋樑,這邊咱們選擇express這個框架。

首先是安裝這兩個包。

npm install --save-dev express webpack-dev-middleware
複製代碼

這個比webpack-dev-server要複雜一些,還須要安裝一個express。可是這個的編譯的內容是會寫入dist文件的,實時更新,徹底按照webpack的編譯來。她的原理就是先執行webpack,在更新到服務器上,這樣咱們訪問的就是最新的內容了。

既然是中間件,那麼就不是webpack親生的,就須要在webpack-dev-server配置的基礎上加點料。

咱們要在須要監控的入口點加入監控的js,像這樣寫:

entry:{
    index:['webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000&reload=true',path.join(__dirname,"/src/index.js")],
},
複製代碼

接着就是server.js的編寫,想要寫好這一部分,你們要先學會express,以及express中間件的用法。而後再是將webpack掛載到express之上。

const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');

const app = express();
const config = require('./webpack.config.js');
const compiler = webpack(config);

app.use(webpackDevMiddleware(compiler, {
  publicPath: config.output.publicPath
}));

app.use(require("webpack-hot-middleware")(compiler));

app.listen(8080, function () {
  console.log('Example app listening on port 8080!\n');
});
複製代碼

這樣配置雖然麻煩,可是咱們能看到實時編譯的JS文件,對於網站的總體細節把控會更好。

總結

感受寫了一篇超長的入門文章,列出了webpack的配置用法,以及webpack插件的用法,能夠說webpack插件是webpack之魂,擴展了許多其餘的功能。還有如何實時編譯咱們的網站。

相關文章
相關標籤/搜索