一份比較詳細的 webpack 4.x 手工配置基礎開發環境 附源碼

webpack

從新書寫了博客內容,但願能夠更好的呈現該有的知識點。
bundle.js 指的是 webpack 打包後的文件。

小劇場

項目經理:咱們要開始一個新的項目,褲襠你來負責項目構建吧。
我:好的沒問題,經理請稍等。javascript

npm install vue-cli -g
vue init webpack -y new-project-name

我:好了,咱們開始吧。
項目經理:接下來呢? css

黑人問號臉

我:接下來沒了,能夠開發了。
項目經理:褲襠啊,速度快是好事,可是我看你每次都是那麼幾步,能不能來點不同的,你看那些面試官,面試手寫一個 webpack 4.x 的配置,你知道怎麼寫麼?
我:。。。。。。
項目經理:(拂袖而去,遠遠地聽到空中傳來一句話)年輕人,切勿急躁,穩中求勝啊。
我:項目急的時候你不是這麼說的。
項目經理:褲襠你說啥?
我:經理你說得對。html

前言

在咱們在面對一個新的項目的時候,網上的大量優秀的模板可使咱們少走不少彎路,能夠把主要的精力放在業務上,等到後期項目龐大了,業務複雜了的時候再去作一些優化,這其中包括項目打包速度優化,項目打包體積優化(也能夠看作是首屏加載優化),等等,可是,身爲一個愛折騰的程序猿,面對這些模板,是的,我很好奇!前端

~~我很好騎~~

固然,文章開始以前,附上該項目的地址,github@jsjzh,全部的代碼我都加上了註釋,但願你們看完以後能夠有所收穫,最好能賞個 star 啦!=3=vue

git clone https://github.com/jsjzh/my-webpack-template.git
cd my-webpack-template
npm install
npm start

webpack 4.x 的那些新玩意兒

在最新的 官方文檔 中,有兩個新的配置項,modeoptimization,咱們就從這兩個入手,看看 webpack .4x 有什麼新東西。java

下面會介紹由於你配置了不一樣的 mode 以後,你的代碼會受到的不一樣對待。node

mode 是個啥

這個配置項是區分 webpack 4.x 和其餘版本最方便的手段,webpack 4.x 給咱們提供了兩個模式用做開發和生產的模式,這兩個模式下 webpack 默默給咱們開啓了很多優化手段,固然,這些優化手段咱們也是能夠配置的,就是在 optimization 這個配置項中,咱們也能夠本身增長 optimization 選項對項目進行更細的優化。webpack

production | development | none

下面我會對 mode 的兩個值 productiondevelopment 進行比較詳細的說明,看看 webpack 到底偷偷給咱們開啓了什麼優化。git

proddev 相同的優化

mode: production || development 時,webpack 都會開啓的優化。github

{
  "mode": "production" || "development",
  "optimization": {
    // 若是 子模塊 和 父模塊 都加載了同一個 A模塊 的時候,開啓這個選項將會告訴 webpack 跳過在 子模塊 中對 A模塊 的檢索,這能夠加快打包速度。
    "removeAvailableModules": true,
    // webpack 將會不會去打包一個空的模塊。
    "removeEmptyChunks": true,
    // 告訴 webpack 合併一些包含了相同模塊的模塊。
    "mergeDuplicateChunks": true,
    // 會在 process.env.NODE_ENV 中傳入當前的 mode 環境。
    "nodeEnv": "production" || "development"
  }
}

proddev 不一樣的優化

mode: production 時,webpack 開啓的優化。

{
  "mode": "production",
  "optimization": {
    // 告訴 webpack 肯定和標記塊,這些塊是其餘塊的子集,當更大的塊已經被加載時,不須要加載這些子集。
    "flagIncludedChunks": true,
    // 告訴 webpack 找出一個模塊的順序,這可使打包出來的入口 bundle.js 最小化。
    "occurrenceOrder": true,
    // 肯定每一個模塊下導出被使用的。
    "usedExports": true,
    // 告訴 webpack 查找能夠安全地鏈接到單個模塊的模塊圖的片斷。取決於優化。
    "concatenateModules": true,
    // 使用 UglifyjsWebpackPlugin 進行代碼壓縮。
    "minimize": true
  },
  // 性能相關配置
  "performance": {
    "hints": "error",
    // ...
  }
}

mode: development 時,webpack 開啓的優化。

{
  "mode": "development",
  // 生成 source map 的格式選擇,這個選項能夠直接影響構建速度。
  "devtool": "eval",
  // 緩存模塊,避免在未更改時重建它們。
  "cache": true,
  "module": {
    // 緩存已解決的依賴項,避免從新解析它們。
    "unsafeCache": true
  },
  "output": {
    // 在 bundle.js 中引入項目所包含模塊的註釋信息。
    "pathinfo": true
  },
  "optimization": {
    // 在可能的狀況下肯定每一個模塊的導出。
    "providedExports": true,
    // 找到 chunk 中共享的模塊,取出來生成單獨的 chunk。
    // 該配置用於代碼分割打包,取代了曾經的 CommonsChunkPlugin 插件。
    "splitChunks": true,
    // 爲 webpack 運行時代碼建立單獨的 chunk。
    "runtimeChunk": true,
    // 編譯錯誤時不寫入到輸出。
    // 取代了曾經的 NoEmitOnErrorsPlugin 插件。
    "noEmitOnErrors": true,
    // 給模塊更有意義更方便調試的名稱。
    // 取代了曾經的 NamedModulesPlugin 插件。
    "namedModules": true,
    // 給 chunk 更有意義更方便調試的名稱。
    "namedChunks": true,
  }
}

webpack 4.x 基礎版開發環境詳細配置

基礎版擁有 npm start 以後 打開一個新的網頁,而且更改 js 會自動更新的功能,不包含對 ES6 語法的轉義以及 css 打包,image 圖片轉爲 dataURL 的功能。

先來一套組合拳,建立一個項目文件夾,並初始化項目。

md my-webpack-template
cd my-webpack-template
npm init -y

接着能夠參考個人目錄結構(列出主要的文件,只針對 dev 環境)。

+---my-webpack-template
|       index.html
|       package.json
+---build
|       utils.js
|       build-server.js
|       webpack.base.conf.js
|       webpack.dev.conf.js
+---config
|       index.js
|       dev.env.js
+---src
|       index.js

安裝所需依賴。

webpackwebpack-cli 曾經是在一塊兒的,在 4.x 版本中進行了拆分,因此若是很差好同時安裝他們倆是不行的哦。

不推薦全局安裝 webpack,這會致使命令行運行 webpack 的時候鎖定版本。
npm install webpack webpack-cli -D

webpack-dev-server 是一個專門用於開發環境使用的集成了衆多功能的環境,基於 express,擁有即時編譯代碼(webapck-dev-middleware),熱更新(webpack-hot-middleware),自動打開瀏覽器(opn),對 HTML5history 作特殊處理(connect-history-api-fallback)等等功能。

對於開發環境,即時編譯的代碼不會存儲在硬盤中而是在內存中,這是由 webapck-dev-middleware 完成的功能。
npm install webpack-dev-server -D

用於合併 webpack 配置的,通常咱們會把 webpackbase 配置和 dev 配置 和 prod 配置分開寫,用這個工具就能夠很方便的合併 basedev 的配置。

npm install webpack-merge -D

一個用於處理打包這個進程的插件,能夠清除打包時候殘留的控制檯信息,而且能夠在控制檯打印出打包成功以後的文字提示,固然,對於打包錯誤以後的回調也是有的。

npm install friendly-errors-webpack-plugin -D

這個相對來講各位看官應該用的不少了吧,用於生成一個 html 文件,而且能夠在底部注入經過 webpack 打包好的 bundle.js 文件。

npm install html-webpack-plugin -D

一個尋找可用端口的工具,當你配置的端口被佔用時,這個工具會自動尋找一個可用的端口。

npm install portfinder -D

配置 package.json 中的運行腳本

接着,安裝完了依賴咱們須要配置 npm 運行時候的腳本了。

當你在命令行直接輸入 webpack 報錯的,而且確信本身已經安裝了 webpack 的時候,試試直接配置 package.json 中的 scripts,說不定你安裝的是項目中的 webpack,而 package.json 中運行的腳本將優先該項目的環境。
"scripts": {
  "dev": "webpack-dev-server --inline --progress --config build/build-server.js",
  "start": "npm run dev"
}

build/webpack.base.conf.js 配置詳解

先來配置 webpack 基礎的配置,這裏的配置 proddev 相同。

// 將一些配置寫在 config/index.js 中,方便直接獲取
var config = require("../config");

// 獲取項目的初始目錄,包裝了個小函數
function resolve(file) {
  return path.resolve(__dirname, "../", file)
}

module.exports = {
  // webpack 處理打包文件的時候的初始目錄
  context: resolve("./"),
  // 入口文件,webapck 4.x 默認的就是 src/index.js
  entry: {
    app: "./src/index.js"
  },
  // 輸出文件的目錄
  output: {
    path: config.build.assetsRoot,
    filename: "[name].js",
    publicPath: process.env.NODE_ENV === "production" ?
      config.build.assetsPublicPath : config.dev.assetsPublicPath
  }
}

build/webpack.dev.conf.js 配置詳解

// 將一些配置寫在 config/index.js 中,方便直接獲取
var config = require("../config");
var devConfig = config.dev;
// 一些工具函數
var utils = require("./utils");
// nodeJs 內置的函數,專門用來解析路徑
var path = require("path");
// 大名鼎鼎的 webpack
var webpack = require("webpack");
var merge = require("webpack-merge");
var HtmlWebpackPlugin = require("html-webpack-plugin");
var webpackBaseConfig = require("./webpack.base.conf");

module.exports = merge(webpackBaseConfig,{
  // 配置開發環境 mode
  mode: "development",
  // 一句話,這是個方便開發工具進行代碼定位的配置
  // 可是不一樣的配置會影響編譯速度和打包速度,這裏使用了和 vue-cli 一樣的配置
  devtool: devConfig.devtool,
  // 使用了 webpack-dev-server 以後就須要有的配置
  // 在這裏能夠配置詳細的開發環境
  devServer: {
    // 當咱們在 package.json 中使用 webpack-dev-server --inline 模式的時候
    // 咱們在 chrome 的開發工具的控制檯 console 能夠看到信息種類
    // 可選 none error warning info
    clientLogLevel: "warning",
    // 不用擔憂:要解決這個問題,你所須要作的就是在你的服務器上添加一個簡單的萬能回退路線。若是URL不匹配任何靜態資產,那麼它應該服務於相同的索引。你的應用程序所在的html頁面。又漂亮! --- by vue-router
    // 這個配置就是應用了 connect-history-api-fallback 插件
    // 想象一個場景,vue 開發,咱們利用 vue-router 的 history 模式進行單頁面中的頁面跳轉
    // www.demo.com 跳轉去 www.demo.com/list
    // 看起來沒毛病,vue-router 中只要配置了 list 的路由便可
    // 可是,當你刷新頁面的時候,瀏覽器會去向服務器請求 www.demo.com/list 的資源,這想固然是找不到的
    // 這個中間件就是會自動捕獲這個錯誤,而後將它從新定位到 index.html
    historyApiFallback: {
      rewrites: [{
        from: /.*/,
        to: path.posix.join(devConfig.assetsPublicPath,"index.html")
      }]
    },
    // webpack 最有用的功能之一 --- by webpack
    // 熱更新裝置啓動
    hot: true,
    // 告訴 webpack-dev-server 搭建服務器的時候從哪裏獲取靜態文件
    // 默認狀況下,將使用當前工做目錄做爲提供靜態文件的目錄
    // contentBase: false,
    // 搭建的開發服務器啓動 gzip 壓縮
    compress: true,
    // 搭建的開發服務器的 host,這裏使用了一個函數去獲取當前電腦的局域網 ip
    // 這個能夠獲取你的電腦的 ip 地址,而後開發服務器就能夠搭建在局域網裏
    // 若是有一同開發的小夥伴,在同一局域網內就能夠直接訪問地址看到你的頁面
    // 一樣,這個也適用於手機,連上同一個 wifi 以後就能夠在手機上實時看到修改的效果
    host: utils.getIPAdress(),
    // 開發服務器的端口號
    // 可是後面咱們會用到 portfinder 插件,若是真的 config/index.js 中的端口被佔用了
    // 那這個插件會以這個爲 basePort 去找一個沒有被佔用的端口
    port: devConfig.port,
    // 是否要服務器搭建完成以後自動打開瀏覽器
    open: devConfig.autoOpenBrowser,
    // 是否打開發現錯誤以後在瀏覽器全屏幕顯示錯誤信息功能
    overlay: devConfig.errorOverlay ? {
      warnings: false,
      errors: true
    } : false,
    // 此路徑下的打包文件可在瀏覽器中訪問
    // 假設服務器運行在 http://localhost:8080 而且 output.filename 被設置爲 bundle.js
    // 默認 publicPath 是 "/",因此 bundle.js 能夠經過 http://localhost:8080/bundle.js 訪問
    publicPath: devConfig.assetsPublicPath,
    // 啓動接口訪問代理
    proxy: devConfig.proxyTable,
    // 啓用 quiet 後,除了初始啓動信息以外的任何內容都不會被打印到控制檯
    // 和 FriendlyErrorsPlugin 配合食用更佳
    quiet: true,
    // 開啓監聽文件修改的功能,在 webpack-dev-server 和 webpack-dev-middleware 中是默認開始的
    // watch: true,
    // 關於上面 watch 的一些選項配置
    watchOptions: {
      // 排除一些文件監聽,這有利於提升性能
      // 這裏排除了 node_modules 文件夾的監聽
      // 可是這在應對須要 npm install 一些新的 module 的時候,就須要重啓服務
      ignored: /node_modules/,
      // 是否開始輪詢,有的時候文件已經更改了可是卻沒有被監聽到,這時候就能夠開始輪詢
      // 可是比較消耗性能,選擇關閉
      poll: devConfig.poll
    }
  },
  plugins: [
    // 這能夠建立一個在編譯過程當中的全局變量
    // 由於這個插件直接執行文本替換,給定的值必須包含字符串自己內的實際引號 --- by webpack
    // 因此須要這麼用
    // "process.env": JSON.stringify('development')
    // 或者
    // "process.env": '"development"'
    new webpack.DefinePlugin({
      "process.env": require("../config/dev.env")
    }),
    // 開啓大名鼎鼎的熱更新插件
    new webpack.HotModuleReplacementPlugin(),
    // 使用大名鼎鼎(詞窮)的 html-webpack-plugin 模板插件
    new HtmlWebpackPlugin({
      // 輸出的 html 文件的名字
      filename: "index.html",
      // 使用的 html 模板名字
      template: "index.html",
      // 是否要插入 weback 打包好的 bundle.js 文件
      inject: true
    })
  ]
})

build/build-server.js 配置詳解

// 更友好的提示插件
var FriendlyErrorsPlugin = require("friendly-errors-webpack-plugin");
// 獲取一個可用的 port 的插件
var portfinder = require("portfinder");
var devWebpackConfig = require("./webpack.dev.conf");

// 導出一個 promise 函數,這可讓 wepback 接受一個異步加載的配置
// 並在 resolve 的時候運行 這個配置
// 好比這裏我就用到了 portfinder 和 friendly-errors-webpack-plugin
module.exports = new Promise((resolve,reject) => {
  // 設置插件的初始搜尋端口號
  portfinder.basePort = devWebpackConfig.devServer.port
  portfinder.getPort((err,port) => {
    if (err) reject(err)
    else {
      // 這裏就利用 portfinder 獲得了可使用的端口
      devWebpackConfig.devServer.port = port
      devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
        // 清除控制檯原有的信息
        clearConsole: true,
        // 打包成功以後在控制檯給予開發者的提示
        compilationSuccessInfo: {
          messages: [`開發環境啓動成功,項目運行在: http://${devWebpackConfig.devServer.host}:${port}`]
        },
        // 打包發生錯誤的時候
        onErrors: () => { console.log("打包失敗") }
      }))
      resolve(devWebpackConfig)
    }
  })
})

編譯出錯了?看看這裏

若是你發現直接在控制檯執行 webpack 報錯了,可是你確實執行了 npm install,那是由於你沒有安裝全局的 webpack。
  • 能夠執行 .\node_modules\.bin\webpack --config webpack.config.js

    • 調用該項目 node_modules 下的 webpack
  • 使用 package.json 配置讓 npm 去找該項目中的 webpack

    • package.json > scripts.build: webapck
DeprecationWarning: Tapable.plugin is deprecated. Use new API on '.hooks' instead

這個錯誤會發生在你使用的插件沒有針對 webpack 4.x 升級。
這個時候只能去 githubissue 或者換一個 plugin 了。

還發現了其餘的錯?請直接私信我,或者在評論中留言。

後語

但願本身所作的一些微小的事情能夠幫助你們在漫漫前端路中更上一層樓,另外,週末了不要太沉迷於敲代碼,多出去走走,散散步,運動運動,給本身的一週充實的大腦放個空。

若是你們以爲我哪裏寫的不對,請不要猶豫,直接 diss 我 =3=

代碼如人生,我甘之如飴。

我在這裏 gayhub@jsjzh 歡迎來找我玩兒

向前看就是將來,向後看就是過去,從中取一段下來就是故事,而這只不過是那樣的故事中很小的一部分而已。--- 灰色的果實

大綱

  • webpack 4.x 的那些新玩意兒(DONE)

    • mode
    • optimization
  • webpack 4.x 基礎版開發環境詳細配置(DONE)

    • package.json 中的 devDependencies
    • package.json 中的 scripts
    • build/webpack.base.conf.js 配置詳解
    • build/webpack.dev.conf.js 配置詳解
    • build/build-server.js 配置詳解
  • webpack 4.x 升級版開發環境詳細配置(TODO)[分篇]

    • 利用 babel 轉換 ES6 語法
    • img 轉爲 dataURL
    • 打包 css
    • 使用 vue-loader 或其餘 loader 來完成更多
    • 本身動手開發一個 webpack-plugin
  • webpack 4.x 生產環境詳細配置(TODO)[分篇]
  • webpack 配置優化(TODO)[分篇]

    • 打包速度優化
    • 打包體積優化
相關文章
相關標籤/搜索