webpack使用記錄

容易混淆概念解析

讀這篇文章理清下面概念 webpack 中那些最易混淆的 5 個知識點javascript

1.module,chunk 和 bundle 的區別是什麼?
2.filename 和 chunkFilename 的區別

版本區別

webpack 2xcss

  • entry
  • output
  • loadershtml

    • file-loader:把文件輸出到一個文件夾中,在代碼中經過相對 URL 去引用輸出的文件
    • url-loader:和 file-loader 相似,可是能在文件很小的狀況下以 base64 的方式把文件內容注入到代碼中去
    • source-map-loader:加載額外的 Source Map 文件,以方便斷點調試
    • image-loader:加載而且壓縮圖片文件
    • babel-loader:把 ES6 轉換成 ES5
    • css-loader:加載 CSS,支持模塊化、壓縮、文件導入等特性
    • style-loader:把 CSS 代碼注入到 JavaScript 中,經過 DOM 操做去加載 CSS。
    • eslint-loader:經過 ESLint 檢查 JavaScript 代碼
  • plugins前端

    • CommonsChunkPlugin:CommonsChunkPlugin主要是用來提取第三方庫和公共模塊,避免首屏加載的bundle文件或者按需加載的bundle文件體積過大,從而致使加載時間過長,着實是優化的一把利器。
    • define-plugin:定義環境變量
    • HtmlWebpackPlugin: 加入 hash 生成html 文件
    • uglifyjs-webpack-plugin:經過UglifyES壓縮ES6代碼
    • webpack-parallel-uglify-plugin: 刪掉webpack提供的UglifyJS插件 以webpack-parallel-uglify-plugin來替換 優化打包速度
    • HotModuleReplacementPlugin: 熱更新, 配合dev-serve中的webpack-dev-middlerware webpack-hot-middleware 插件使用
    • NoEmitOnErrorsPlugin: 在編譯出現錯誤時,使用 NoEmitOnErrorsPlugin 來跳過輸出階段。這樣能夠確保輸出資源不會包含錯誤。對於全部資源,統計資料(stat)的 emitted 標識都是 false。
    • ExtractTextPlugin:該插件的主要是爲了抽離css樣式,防止將樣式打包在js中引發頁面樣式加載錯亂的現象。
    • imagemin-webpack-plugin: This is a simple plugin that uses Imagemin to compress all images in your project.

webpack 4xvue

自動生成html文件java

let HtmlWebpackPlugin = require('html-webpack-plugin');
 new HtmlWebpackPlugin({
    template: './src/index.html',    // 模板文件,不侷限html後綴
    filename: 'index.html',  // 生成的html命名
    hash: true, //  會在打包好的bundle.js後加hash串
    chunks: [name]   // 指定輸出頁面須要引入的chunks
    // removeComments 移除HTML中的註釋
    // collapseWhitespace 刪除空白符與換行符,整個文件會壓成一行
    // inlineSource 插入到html的css,js文件都要內聯,既不是以link,script引入
    // inject 是否能注入內容到輸入的頁面去
  })

新手指南

從零開始

npm init
npm install webpack --save-dev //webpack@4.16.1
npm install webpack-dev-server --save-dev //webpack-dev-server@3.1.4

建立webpack.config.jsnode

var config = {

}

module.exports = config

注:這裏的module.exports = config至關於export default config ,因爲目前尚未安裝支持ES6的編譯插件,所以不能直接使用ES6的語法 不然會報錯jquery

package.json的scripts裏添加一個快速啓動webpack-dev-server服務腳本:webpack

圖片描述
npm run dev 後默認打開的地址是: 127.0.0.1:8080
能夠本身配置:ios

"dev": "webpack-dev-server --host 197.163.1.2 --port 8888 --open --config webpack.config.js"
// webpack.config.js
var path = require('path')
module.exports = {
    entry: { //告訴webpack從哪裏找依賴並編譯
        main: './main'
    },
    output: { // 配置編譯的文件儲存位置和文件名
        path: path.join(__dirname, './dist'),
        publicPath: '/dist/',
        filename: 'main.js'
    }
};

圖片描述

// index.html
<!DOCTYPE html>
<html>
<head>
    <title>webpack test</title>
</head>
<body>
   <div id="app">您好, 鏡心的小樹屋歡迎您</div>
   <script type="text/javascript" src="/dist/main.js"></script>
</body>
</html>

npm run dev 啓動
若是報錯:

圖片描述

npm install webpack-cli -D  // "webpack-cli": "^3.0.8",
 npm run dev

圖片描述

main.js

document.getElementById('app').innerHTML = "你好美"

圖片描述

進行打包

webpack --progress -- hide-modules// 會生成一個/dist/main.js

完善配置文件

https://github.com/icarusion/...

// css加載器
npm install css-loader --save-dev // style-loader@0.21.0

npm install style-loader --save-dev // css-loader@1.0.0

安裝後,在webpack.config.js文件配置Loader,添加.css文件處理

圖片描述
module對象rules屬性中能夠指定一系列loaders。
use選型的值能夠是數組或字符串,若是是數組,它的編譯順序就是從後往前。
extract-text-webpack-plugin插件是用來把散落在各地的css提取出來,並生成一個main.css文件,最終在index.html經過<link>加載

npm install extract-text-webpack-plugin --save-dev
var path = require('path');
var ExtractTextPlugin = require('extract-text-webpack-plugin');

var config = {
    entry: {
        main: './main'
    },
    output: {
        path: path.join(__dirname, './dist'),
        publicPath: '/dist/',
        filename: 'main.js'
    },
    module: {
        rules: [
            {
                test: /\.vue$/,
                loader: 'vue-loader',
                options: {
                    loaders: {
                        css: ExtractTextPlugin.extract({
                            use: 'css-loader',
                            fallback: 'vue-style-loader'
                        })
                    }
                }
            },
            {
                test: /\.js$/,
                loader: 'babel-loader',
                exclude: /node_modules/
            },
            {
                test: /\.css$/,
                use: ExtractTextPlugin.extract({
                    use: 'css-loader',
                    fallback: 'style-loader'
                })
            },
            {
                test: /\.(gif|jpg|png|woff|svg|eot|ttf)\??.*$/,
                loader: 'url-loader?limit=1024'
            }
        ]
    },
    plugins: [
    // 重命名提取後的css文件
        new ExtractTextPlugin({
            filename: '[name].css',
            allChunks: true
        })
    ]
};

module.exports = config;

快速構建

clipboard.png

快速構建最好的天然是使用腳手架, 你能夠選擇:

Webpack boilerplate CLI: https://www.npmjs.com/package...
easywebpack-cli: https://yuque.com/hubcarl/ves...
vue-cli: https://github.com/vuejs/vue-cli

腳手架中的 webpack 配置

咱們這裏將以vue-cli3爲例構建一個vue項目,(vue-cli3提供了極其完整的配置來幫助開發者快捷開始一個項目)
同時嘗試修改這些配置以知足特殊的需求。

clipboard.png

clipboard.png

clipboard.png
升級nodejs

clipboard.png
從新安裝

clipboard.png
9.0.0 也不能用,換成最新版

clipboard.png

clipboard.png

clipboard.png

單文件系統 與 vue-loader

先看這個:https://vue-loader.vuejs.org/... 瞭解文檔內容

npm install --save vue  // vue@2.5.16
npm install --save-dev vue-loader vue-style-loader vue-template-compiler vue-hot-reload-api babel babel-loader babel-core babel-plugin-transform-runtime babel-preset-es2015 babel-runtime

VUE-CLI 3 :https://cli.vuejs.org/guide/installation.html

修改webpack.config.js 支持.vue es6

var path = require('path')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
module.exports = {
    entry: { //告訴webpack從哪裏找依賴並編譯
        main: './main'
    },
    output: { // 配置編譯的文件儲存位置和文件名
        path: path.join(__dirname, './dist'),//  打包後的輸出目錄
        publicPath: '/dist/',// 指定資源文件引用目錄(若是資源在cdn上 能夠是cdn網址)
        filename: 'main.js'//
    },
    module: {
        rules: [
          {
            test: /\.vue$/,
            loader: 'vue-loader',
            options: {
                loaders: {
                    css: ExtractTextPlugin.extract({
                        use: 'css-loader',
                        fallback: 'vue-style-loader'
                    })
                }
            }
          },
          {
            test: /\.js$/,
            loader: 'babel-loader',
            exclude: /node_modules/
          },
          {
            test: /\.css$/,
            use: ExtractTextPlugin.extract({
                use: 'css-loader',
                fallback: 'vue-style-loader'
            })
          }

        ]
    },
    plugins: [
      new ExtractTextPlugin("main.css")
    ]
};

根目錄新建.babelrc

{
    "presets": ["es2015"],
    "plugin": ["transform-runtime"],
    "comments": false
}

注意: 此種設置須要把webpack降級到2x:
webpack: 2.3.2
webpack-dev-server: 2.4.2

咱們看看實際項目中依賴(webpack2的)

{
  "name": "test",
  "version": "1.0.0",
  "description": "A Vue.js project",
  "author": "AlexZ33",
  "private": true,
  "scripts": {
    "dev": "node build/dev-server.js",
    "build": "node build/build.js",
    "lint": "eslint --ext .js,.vue src",
    "test": "mocha test/*.js"
  },
  "dependencies": {
    "axios": "^0.16.1",
    "d3": "^4.8.0",
    "d3-cloud": "^1.2.4",
    "echarts": "^3.5.2",
    "echarts-wordcloud": "^1.1.0",
    "iview": "^2.5.1",
    "jquery": "^3.3.1",
    "jsonp": "^0.2.1",
    "lodash.merge": "^4.6.1",
    "promise-polyfill": "^6.0.2",
    "shave": "^2.1.3",
    "url-search-params": "^0.9.0",
    "vue": "^2.3.4",
    "vue-router": "^2.2.0",
    "vue-select": "^2.4.0",
    "vuex": "^2.4.0",
    "wordcloud": "^1.0.6"
  },
  "devDependencies": {
    "autoprefixer": "^6.7.2",
    "babel-core": "^6.22.1",
    "babel-eslint": "^7.1.1",
    "babel-loader": "^6.2.10",
    "babel-plugin-transform-runtime": "^6.22.0",
    "babel-preset-env": "^1.2.1",
    "babel-preset-stage-2": "^6.22.0",
    "babel-register": "^6.22.0",
    "chai": "^4.0.2",
    "chalk": "^1.1.3",
    "connect-history-api-fallback": "^1.3.0",
    "copy-webpack-plugin": "^4.0.1",
    "css-loader": "^0.26.1",
    "eslint": "^3.14.1",
    "eslint-config-standard": "^6.2.1",
    "eslint-friendly-formatter": "^2.0.7",
    "eslint-loader": "^1.6.1",
    "eslint-plugin-html": "^2.0.0",
    "eslint-plugin-promise": "^3.4.0",
    "eslint-plugin-standard": "^2.0.1",
    "eventsource-polyfill": "^0.9.6",
    "express": "^4.14.1",
    "extract-text-webpack-plugin": "^2.0.0",
    "file-loader": "^0.10.0",
    "friendly-errors-webpack-plugin": "^1.1.3",
    "fullpage.js": "^2.9.4",
    "function-bind": "^1.1.0",
    "html-webpack-plugin": "^2.28.0",
    "http-proxy-middleware": "^0.17.3",
    "less": "^2.7.2",
    "less-loader": "^4.0.3",
    "mocha": "^3.4.2",
    "node-sass": "^4.9.0",
    "opn": "^4.0.2",
    "optimize-css-assets-webpack-plugin": "^1.3.0",
    "ora": "^1.1.0",
    "rimraf": "^2.6.0",
    "sass-loader": "^6.0.3",
    "semver": "^5.3.0",
    "shelljs": "^0.7.7",
    "url-loader": "^0.5.7",
    "vue-loader": "^12.2.1",
    "vue-style-loader": "^2.0.0",
    "vue-template-compiler": "^2.3.4",
    "webpack": "^2.2.1",
    "webpack-bundle-analyzer": "^2.2.1",
    "webpack-dev-middleware": "^1.10.0",
    "webpack-hot-middleware": "^2.16.1",
    "webpack-merge": "^2.6.1"
  },
  "engines": {
    "node": ">= 4.0.0",
    "npm": ">= 3.0.0"
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not ie <= 8"
  ]
}

webpack+vue-cil 中proxyTable配置接口地址代理

在項目開發的時候,接口聯調的時候通常都是同域名下,且不存在跨域的狀況下進行接口聯調,可是當咱們如今使用vue-cli進行項目打包的時候,咱們在本地啓動服務器後,好比本地開發服務下是 http://localhost:8080 這樣的訪問頁面,可是咱們的接口地址是 http://xxxx.com/save/index 這樣的接口地址,咱們這樣直接使用會存在跨域的請求,致使接口請求不成功,所以咱們須要在打包的時候配置一下,咱們進入 config/index.js 代碼下以下配置便可:

dev: {

    // 靜態資源文件夾
    assetsSubDirectory: 'static',

    // 發佈路徑
    assetsPublicPath: '/',

    // 代理配置表,在這裏能夠配置特定的請求代理到對應的API接口
    // 例如將'localhost:8080/api/xxx'代理到'www.example.com/api/xxx'
    // 使用方法:https://vuejs-templates.github.io/webpack/proxy.html
    proxyTable: {
      '/api': {
        target: 'http://xxxxxx.com', // 接口的域名
        // secure: false,  // 若是是https接口,須要配置這個參數
        changeOrigin: true, // 若是接口跨域,須要進行這個參數配置
        pathRewrite: {
          '^/api': ''
        }
      }
    },

    // 本地訪問 http://localhost:8080
    host: 'localhost', // can be overwritten by process.env.HOST

接口地址本來是 /save/index,可是爲了匹配代理地址,在前面加一個 /api, 所以接口地址須要寫成這樣的便可生效 /api/save/index。

注意: '/api' 爲匹配項,target 爲被請求的地址,由於在 ajax 的 url 中加了前綴 '/api',而本來的接口是沒有這個前綴的,因此須要經過 pathRewrite 來重寫地址,將前綴 '/api' 轉爲 '/'。若是自己的接口地址就有 '/api' 這種通用前綴,就能夠把 pathRewrite 刪掉。

常見示例如在 :https://github.com/bailicangd... 這裏加

proxyTable: {
      // '/pre-web': 'http://118.24.153.55/' // 線上
      '/pre-web': 'http://118.24.153.56' // 測試
      // 'pre-web': 'http://118.24.153.57' // 本地服務器
    },

單頁面的多頁面切換

按需加載

經常使用插件

DefinePlugin

-> 用法戳 文檔
圖片描述
圖片描述

熱更新插件

使用

  • Webpack 配置添加 HotModuleReplacementPlugin 插件

clipboard.png

  • Node Server 引入 webpack-dev-middlerwarewebpack-hot-middleware 插件

clipboard.png

require('./check-versions')()

var config = require('../config')
if (!process.env.NODE_ENV) {
  process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
}

var opn = require('opn')
var path = require('path')
var express = require('express')
var webpack = require('webpack')
var proxyMiddleware = require('http-proxy-middleware')
var webpackConfig = require('./webpack.dev.conf')

// 端口配置
var port = process.env.PORT || config.dev.port
// 打開瀏覽器
var autoOpenBrowser = !!config.dev.autoOpenBrowser
// 代理配置
var proxyTable = config.dev.proxyTable

var app = express()
var compiler = webpack(webpackConfig)
// 本地服務配置
var devMiddleware = require('webpack-dev-middleware')(compiler, {
  publicPath: webpackConfig.output.publicPath,
  quiet: true
})
//載入熱加載配置
var hotMiddleware = require('webpack-hot-middleware')(compiler, {
  log: () => {}
})
// 配置view層代碼啓用熱載
compiler.plugin('compilation', function (compilation) {
  compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
    hotMiddleware.publish({ action: 'reload' })
    cb()
  })
})

// 載入代理配置
Object.keys(proxyTable).forEach(function (context) {
  var options = proxyTable[context]
  if (typeof options === 'string') {
    options = { target: options }
  }
  app.use(proxyMiddleware(options.filter || context, options))
})

// 回退配置
app.use(require('connect-history-api-fallback')())

// 載入開發服務配置
app.use(devMiddleware)

// 載入熱加載配置
app.use(hotMiddleware)

// 掛載靜態資源
var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
app.use(staticPath, express.static('./static'))

var uri = 'http://localhost:' + port

var _resolve
var readyPromise = new Promise(resolve => {
  _resolve = resolve
})

console.log('> 啓動本地開發服務...')
devMiddleware.waitUntilValid(() => {
  console.log('> 服務地址 ' + uri + '\n')
  _resolve()
})

// 開啓 express 服務
var server = app.listen(port)

module.exports = {
  ready: readyPromise,
  close: () => {
    server.close()
  }
}
  • 若是是 koa 引入對應的 koa-webpack-dev-middlerwarekoa-webpack-hot-middleware
const devMiddleware = require('koa-webpack-dev-middleware')(compiler, {
  publicPath,
   stats: {
    colors: true,
    children: true,
    modules: false,
    chunks: false,
    chunkModules: false,
  },
  watchOptions: {
    ignored: /node_modules/,
  }
});

app.use(devMiddleware);
const hotMiddleware = require('koa-webpack-hot-middleware')(compiler, {
   log: false,
   reload: true
});
app.use(hotMiddleware);
  • entry 注入熱更新代碼
webpack-hot-middleware/client?path=http://127.0.0.1:9000/__webpack_hmr&noInfo=false&reload=true&quiet=false

這裏注入熱更新代碼是關鍵,保證服務端和客戶端可以通訊。

熱更新原理

圖片描述

瀏覽器的網頁經過websocket協議與服務器創建起一個長鏈接,當服務器的css/js/html進行了修改的時候,服務器會向前端發送一個更新的消息,若是是css或者html發生了改變,網頁執行js直接操做dom,局部刷新,若是是js發生了改變,只好刷新整個頁面
  • js發生改變的時候,不太可能判斷出對dom的局部影響,只能全局刷新
  • 爲什麼沒有提到圖片的更新?若是是在html或者css裏修改了圖片路徑,那麼更新html和css,只要圖片路徑沒有錯,那麼就已經達到了更新圖片的路徑。若是是相同路徑的圖片進行了替換,這每每須要重啓下服務

在簡單的網頁應用中,這一個過程可能僅僅是節省了手動刷新瀏覽器的繁瑣,可是在負責的應用中,若是你在調試的部分須要從頁面入口操做好幾步纔到達,例如:登陸->列表->詳情->彈出窗口,那麼局部刷新將大大提升調試效率

常見面試問題

  • gulp/grunt 與 webpack的區別是什麼?
  • webpack plugin loader區別
  • webpack是解決什麼問題而生的?
  • 如何配置多入口文件?
  • 你是如何提升webpack構件速度的?
  • 利用webpack如何優化前端性能?

遷移

關於vue-cli2項目

'Real' Static Assets

static/目錄下的文件並不會被webpack處理,它們會被複制到最終目錄(默認是dist/static下),必須使用絕對路徑引用這些文件,這是經過在config.js文件中的build.assetsPublicPathbuild.assetsSubDirectory鏈接來肯定的
任何放在 static/中文件須要以絕對路徑的形式引用 /static/[filename].
若是更assetSubDirectory的值爲assets,那麼路徑需改成/assets/[filename]

src/assets裏面的會被webpack打包進你的代碼裏,而static裏面的,就直接引用了,
通常static裏面放一些類庫的文件,在assets裏面放屬於該項目的資源文件。

build/

在vue-cli2構建的項目中,buildconfig是項目的構建和配內容

clipboard.png

webpack.base.conf.js

var path = require('path')
var utils = require('./utils')
var config = require('../config')
var vueLoaderConfig = require('./vue-loader.conf')
var webpack = require("webpack")

// webpack 基礎文件配置
function resolve (dir) {
  return path.join(__dirname, '..', dir)
}
module.exports = {
  entry: {
    app: './src/main.js'
  },
  plugins: [
    new webpack.ProvidePlugin({
      jQuery: "jquery",
      $: "jquery"
    })
  ],
  output: {
    path: config.build.assetsRoot,
    filename: '[name].js',
    publicPath: process.env.NODE_ENV === 'production'
      ? config.build.assetsPublicPath
      : config.dev.assetsPublicPath
  },
  resolve: {
    extensions: ['.js', '.vue', '.json'],
    modules: [
      resolve('src'),
      resolve('node_modules'),
      resolve('static')
    ],
    alias: {
      'vue$': 'vue/dist/vue.min.js',
      '@': resolve('src'),
      'echart': 'echart/dist/echarts.common.min.js'
    }
  },
  module: {
    rules: [
      {
        test: /\.(js|vue)$/,
        loader: 'eslint-loader',
        enforce: 'pre',
        include: [resolve('src'), resolve('test')],
        options: {
          formatter: require('eslint-friendly-formatter')
        }
      },
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: vueLoaderConfig,
        exclude: /node_modules/
      },
      
      {
        test: /\.js$/,
        loader: 'babel-loader',
        // 把對.js 的文件處理交給id爲happyBabel 的HappyPack 的實例執行
        // loader: 'happypack/loader?id=happyBabel',
        include: [resolve('src'), resolve('test'), resolve('static')],
        exclude: /node_modules/
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('img/[name].[hash:7].[ext]')
        }
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
        }
      }
    ]
  }
}

webpack.dev.conf.js

var utils = require('./utils')
var webpack = require('webpack')
var config = require('../config')
var merge = require('webpack-merge')
var baseWebpackConfig = require('./webpack.base.conf')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')

// 配置 需熱載文件
Object.keys(baseWebpackConfig.entry).forEach(function (name) {
  baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
})

module.exports = merge(baseWebpackConfig, {
  module: {
    rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
  },
  resolve: {
    alias: {
      'vue$': 'vue/dist/vue.js',
    }
  },
  // devtool: '#source-map',
  // devtool: '#eval-source-map',
  devtool: 'cheap-module-eval-source-map',
  // devtool: false,
  plugins: [
    new webpack.DefinePlugin({
      'process.env': config.dev.env
    }),
    // https://github.com/glenjamin/webpack-hot-middleware#installation--usage
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoEmitOnErrorsPlugin(),
    // https://github.com/ampedandwired/html-webpack-plugin
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: 'index.html',
      title: config.dev.title,
      favicon: config.dev.favicon,
      inject: true
    }),
    new FriendlyErrorsPlugin()
  ]
})

webpack.prod.conf.js

var path = require('path')
var utils = require('./utils')
var webpack = require('webpack')
var config = require('../config')
var merge = require('webpack-merge')
var baseWebpackConfig = require('./webpack.base.conf')
var CopyWebpackPlugin = require('copy-webpack-plugin')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
var ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin')

var env = config.build.env

var webpackConfig = merge(baseWebpackConfig, {
  module: {
    rules: utils.styleLoaders({
      sourceMap: config.build.productionSourceMap,
      extract: true
    })
  },
  devtool: config.build.productionSourceMap ? '#source-map' : false,
  output: {
    path: config.build.assetsRoot,
    filename: utils.assetsPath('js/[name].[chunkhash].js'),
    chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
  },
  resolve: {
    alias: {
      'vue$': 'vue/dist/vue.min.js'
    }
  },
  plugins: [
    // http://vuejs.github.io/vue-loader/en/workflow/production.html
    //去除重複引用
    new webpack.DefinePlugin({
      'process.env': env
    }),
    // 刪掉webpack提供的UglifyJS插件 以webpack-parallel-uglify-plugin來替換 優化打包速度
    // new webpack.optimize.UglifyJsPlugin({
    //   compress: {
    //     warnings: false
    //   },
    //   sourceMap: true
    // }),
    // 增長 webpack-parallel-uglify-plugin
    new ParallelUglifyPlugin({
      cacheDir: '.cache/',
      uglifyJs: {
        output: {
          comments: false
        },
        compress: {
          warnings: false
        }
      }
    }),
    // 添加DllReferencePlugin插件
    new webpack.DllReferencePlugin({
      context: path.resolve(__dirname, '..'),
      manifest: require('./vendor-manifest.json')
    }),
    // 生產單個樣式文件
    new ExtractTextPlugin({
      filename: utils.assetsPath('css/[name].[contenthash].css')
    }),

    new OptimizeCSSPlugin({
      cssProcessorOptions: {
        safe: true
      }
    }),
    // 加入 hash 生成html 文件
    new HtmlWebpackPlugin({
      filename: config.build.index,
      template: 'index.html',
      inject: true,
      title: config.build.title,
      favicon: config.build.favicon,
      minify: {
        // 刪除註釋
        removeComments: true,
        // 合併空白
        collapseWhitespace: true,
        // 刪除屬性的引號
        removeAttributeQuotes: true
        // https://github.com/kangax/html-minifier#options-quick-reference
      },
      // 容許控制塊在添加到頁面以前的排序方式
      // dependency 按照依賴關係添加
      chunksSortMode: 'dependency'
    }),
    // new HtmlWebpackPlugin({
    //   filename: 'update-browser.html',
    //   template: 'update-browser.html'
    // }),
    // 分離類庫文件
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      minChunks: function (module, count) {
        return (
          module.resource &&
          /\.js$/.test(module.resource) &&
          module.resource.indexOf(
            path.join(__dirname, '../node_modules')
          ) === 0
        )
      }
    }),
    // 分離 webpack run time 文件
    new webpack.optimize.CommonsChunkPlugin({
      name: 'manifest',
      chunks: ['vendor']
    }),
    // copy自定義文件
    new CopyWebpackPlugin([
      {
        from: path.resolve(__dirname, '../static'),
        to: config.build.assetsSubDirectory,
        ignore: ['.*']
      }
    ])
  ]
})

if (config.build.productionGzip) {
  var CompressionWebpackPlugin = require('compression-webpack-plugin')

  webpackConfig.plugins.push(
    new CompressionWebpackPlugin({
      asset: '[path].gz[query]',
      algorithm: 'gzip',
      test: new RegExp(
        '\\.(' +
        config.build.productionGzipExtensions.join('|') +
        ')$'
      ),
      threshold: 10240,
      minRatio: 0.8
    })
  )
}

if (config.build.bundleAnalyzerReport) {
  var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
  webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}

module.exports = webpackConfig

webpack.dll.conf.js

預編譯,優化打包速度的

//DllPlugin 插件:做用是預先編譯一些模塊。
//DllReferencePlugin 插件:它的所用則是把這些預先編譯好的模塊引用起來。
var path = require('path')
var webpack = require('webpack')

module.exports = {
  entry: {
    vendor: [
      'vue/dist/vue.common.js',
      'vue-router',
      'vuex',//提早打包一些基本不怎麼修改的文件
  ]
  },
  output: {
    path: path.join(__dirname, '../static/js'),//放在項目的static/js目錄下面
    filename: '[name].dll.js', // 打包文件的名字
    library: '[name]_library' // 可選 暴露出的全局變量名
    // vendor.dll.js中暴露出的全局變量名。
    // 主要是給DllPlugin中的name使用,
    // 故這裏須要和webpack.DllPlugin中的`name: '[name]_library',`保持一致。
  },
  plugins: [
    // 注意:DllPlugin 必需要在 DllReferencePlugin 執行前先執行一次,dll 這個概念應該也是借鑑了 windows 程序開發中的 dll 文件的設計理念。
    new webpack.DllPlugin({
      path: path.join(__dirname, '.', '[name]-manifest.json'),//生成上文說到清單文件,放在當前build文件下面
      name: '[name]_library'
    }),
    //壓縮 只是爲了包更小一點
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false,
        drop_console: true,
        drop_debugger: true
      },
      output: {
        // 去掉註釋
        comments: false
      },
      sourceMap: true
    })
  ]
}

dev-server.js

require('./check-versions')()

var config = require('../config')
if (!process.env.NODE_ENV) {
  process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
}

var opn = require('opn')
var path = require('path')
var express = require('express')
var webpack = require('webpack')
var proxyMiddleware = require('http-proxy-middleware')
var webpackConfig = require('./webpack.dev.conf')

// 端口配置
var port = process.env.PORT || config.dev.port
// 打開瀏覽器
var autoOpenBrowser = !!config.dev.autoOpenBrowser
// 代理配置
var proxyTable = config.dev.proxyTable

var app = express()
var compiler = webpack(webpackConfig)
// 本地服務配置
var devMiddleware = require('webpack-dev-middleware')(compiler, {
  publicPath: webpackConfig.output.publicPath,
  quiet: true
})
//載入熱加載配置
var hotMiddleware = require('webpack-hot-middleware')(compiler, {
  log: () => {}
})
// 配置view層代碼啓用熱載
compiler.plugin('compilation', function (compilation) {
  compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
    hotMiddleware.publish({ action: 'reload' })
    cb()
  })
})

// 載入代理配置
Object.keys(proxyTable).forEach(function (context) {
  var options = proxyTable[context]
  if (typeof options === 'string') {
    options = { target: options }
  }
  app.use(proxyMiddleware(options.filter || context, options))
})

// 回退配置
// https://blog.csdn.net/astonishqft/article/details/82762354
app.use(require('connect-history-api-fallback')())

// 載入開發服務配置
app.use(devMiddleware)

// 載入熱加載配置
app.use(hotMiddleware)

// 掛載靜態資源
var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
app.use(staticPath, express.static('./static'))

var uri = 'http://localhost:' + port

var _resolve
var readyPromise = new Promise(resolve => {
  _resolve = resolve
})

console.log('> 啓動本地開發服務...')
devMiddleware.waitUntilValid(() => {
  console.log('> 服務地址 ' + uri + '\n')
  _resolve()
})

// 開啓 express 服務
var server = app.listen(port)

module.exports = {
  ready: readyPromise,
  close: () => {
    server.close()
  }
}

vue項目 migrate to webpack v4 from v2

  • webpack 升級 (npm i webpack@4 -D)
  • webpack-cli
  • webpack-dev-server@3
  • html-webpack-plugin (npm i html-webpack-plugin@3.2.0 -D)
  • babel-loader(npm i babel-loader@7 -D)
  • vue-loader(npm i vue-loader@15 -D)
  • style-loader 最新
  • css-loader 最新
  • vue-style-loader 最新

    • (若是有sass-loaderless-loader,也更新最新)
  • url-loader@1
  • file-loader@1
  • ts-loader 更新最新
  • extract-text-webpack-plugin的beta包 (npm i extract-text-webpack-plugin@next -D) 或者 mini-css-extract-plugin
  • mini-css-extract-plugin

遇到的問題


clipboard.png

文件下添加了process.traceDeprecation = true

clipboard.png
運行 npm run dev,按照webpack.dev.conf.js配置跑下dev-server,發現

clipboard.png
升級下

clipboard.png
再跑,發現

clipboard.png
更新

npm install webpack-dev-middleware@3 -D

javaScript File Code Spliting 代碼分離

Webpack打包是把全部js代碼打成一個js文件,咱們能夠經過 CommonsChunkPlugin 分離出公共組件,但這遠遠不夠。 實際業務開發時,一些主要頁面內容每每比較多, 並且會引入第三方組件。其中有些內容的展現再也不首屏或者監控腳本等對用戶不是那麼重要的腳本咱們能夠經過 require.ensure 代碼分離延遲加載。在 Webpack在構建時,解析到require.ensure 時,會單獨針對引入的js資源單獨構建出chunk文件,這樣就能從主js文件裏面分離出來。 而後頁面加載完後, 經過script標籤的方式動態插入到文檔中。

require.ensure 使用方式, 第三個參數是指定生產的chunk文件名,不設置時是用數字編號代理。相同require.ensure只會生產一個chunk文件。

require.ensure(['swiper'], ()=> {
  const Swiper = require('swiper');
  ......
 }, 'swiper');

Vue Component Code Spliting 代碼分離

  • 咱們在用 Vue 開發業務時,每每是把某個功能封裝成 Vue 組件,Vue 動態組件加載 相比 純js 加載更有實際意義。
  • 異步加載 Vue 組件(.vue) 已在 Vue 2.5+ 版本支持,包括路由異步加載和非路由異步加載。在具體實現時,咱們能夠經過 import(filepath) 加載組件。
  • Webpack 已經支持了該特性。import() 返回的 Promise,經過註釋 webpackChunkName 指定生成的 chunk 名稱。 Webpack 構建時會獨立的 chunkjs 文件,而後在客戶端動態插入組件,chunk 機制與 require.ensure 同樣。有了動態加載的方案,能夠減小服務端渲染 jsbundle 文件的大小,頁面 Vue 組件模塊也能夠按需加載。
Vue.component('async-swiper', (resolve) => {
  // 經過註釋webpackChunkName 指定生成的chunk名稱
  import(/* webpackChunkName: "asyncSwiper" */ './AsyncSwiper.vue')
});

<div id="app">
<p>Vue dynamic component load</p><async-swiper></async-swiper> 
</div>

其實這裏說的就是 vue-router裏的 路由懶加載
實際spa項目中:

參考

webpack 指南
4.30版本中文文檔

《vue實戰》
vue-loader: https://vue-loader.vuejs.org/...
https://www.jianshu.com/p/f05...
https://www.cnblogs.com/tugen...
http://vuejs-templates.github...
性能優化篇---Webpack構建代碼質量壓縮
Webpack 4 和單頁應用入門

Webpack 熱更新實現原理分析
前端開發熱更新原理解讀
實現個簡單webpack
詳解CommonsChunkPlugin的配置和用法
connect-history-api-fallback庫的理解
To v4 from v3 文檔

Egg + Vue SSR 組件異步加載

相關文章
相關標籤/搜索