webpack:從入門到真實項目配置

弄了博客很久了,也沒有時間寫。今天在網上看着一篇文章,關於webpack的,寫的很不錯。(原文連接:https://juejin.im/user/574f8d8d2e958a005fd4edac)隨手記錄一下吧。css

Webpack究竟是什麼?html

自從出現模塊化之後,你們能夠將本來一坨代碼分離到個個模塊中,可是由此引起了一個問題。每一個 JS 文件都須要從服務器去拿,由此會致使加載速度變慢。Webpack 最主要的目的就是爲了解決這個問題,將全部小文件打包成一個或多個大文件,官網的圖片很好的詮釋了這個事情,除此以外,Webpack 也是一個能讓你使用各類前端新技術的工具。前端

 

 

那麼咱們開始簡單的介紹下如何使用這個webpack吧node

 

簡單使用

安裝

在命令行中依次輸入react




而後按照下圖建立文件mkdir webpack-demo cd webpack-demo // 建立 package.json,這裏會問一些問題,直接回車跳過就行 npm init // 推薦這個安裝方式,固然你也安裝在全局環境下 // 這種安裝方式會將 webpack 放入 devDependencies 依賴中 npm install --save-dev webpack

 

在如下文件寫入代碼webpack

// sum.js // 這個模塊化寫法是 node 環境獨有的,瀏覽器原生不支持使用 module.exports = function(a, b) { return a + b } // index.js var sum = require('./sum') console.log(sum(1, 2))
<!DOCTYPE html> <html lang="en"> <head> <title>Document</title> </head> <body> <div id="app"></div> <script src="./build/bundle.js"></script> </body> </html>

如今咱們開始配置最簡單的 webpack,首先建立 webpack.config.js 文件,而後寫入以下代碼git

// 自帶的庫 const path = require('path') module.exports = { entry: './app/index.js', // 入口文件 output: { path: path.resolve(__dirname, 'build'), // 必須使用絕對地址,輸出文件夾 filename: "bundle.js" // 打包後輸出文件的文件名 } }

如今咱們能夠開始使用 webpack 了,在命令行中輸入github

node_modules/.bin/webpack

沒問題的話你應該能夠看到相似的樣子web

 

能夠發現本來兩個 JS 文件只有 100B,可是打包後卻增加到 2.66KB,這之中 webpack 確定作了什麼事情,咱們去 bundle.js 文件中看看。npm

把代碼簡化之後,核心思路是這樣的

var array = [(function () { var sum = array[1] console.log(sum(1, 2)) }), (function (a,b) { return a + b }) ] array[0]() // -> 3

由於 module.export 瀏覽器是不支持的,因此 webpack 將代碼改爲瀏覽器能識別的樣子。如今將 index.html 文件在瀏覽器中打開,應該也能夠看到正確的 log。

咱們以前是在文件夾中安裝的 webpack,每次要輸入 node_modules/.bin/webpack 過於繁瑣,能夠在 package.json 以下修改

"scripts": { "start": "webpack" },

而後再次執行 npm run start,能夠發現和以前的效果是相同的。簡單的使用到此爲止,接下來咱們來探索 webpack 更多的功能。

Loader

Loader 是 webpack 一個很強大功能,這個功能可讓你使用不少新的技術。

Babel

Babel 可讓你使用 ES2015/16/17 寫代碼而不用顧忌瀏覽器的問題,Babel 能夠幫你轉換代碼。首先安裝必要的幾個 Babel 庫

npm i --save-dev babel-loader babel-core babel-preset-env

先介紹下咱們安裝的三個庫

  • babel-loader 用於讓 webpack 知道如何運行 babel
  • babel-core 能夠看作編譯器,這個庫知道如何解析代碼
  • babel-preset-env 這個庫能夠根據環境的不一樣轉換代碼

接下來更改 webpack-config.js 中的代碼

module.exports = { // ...... module: { rules: [ { // js 文件才使用 babel test: /\.js$/, // 使用哪一個 loader use: 'babel-loader', // 不包括路徑 exclude: /node_modules/ } ] } }

配置 Babel 有不少方式,這裏推薦使用 .babelrc 文件管理。

// ..babelrc { "presets": ["babel-preset-env"] }

如今將以前 JS 的代碼改爲 ES6 的寫法

// sum.js export default (a, b) => { return a + b } // index.js import sum from './sum' console.log(sum(1, 2))

執行 npm run start,再觀察 bundle.js 中的代碼,能夠發現代碼被轉換過了,而且一樣能夠正常 輸出3。

固然 Babel 遠不止這些功能,有興趣的能夠前往官網本身探索。

處理圖片

這一小節咱們將使用 url-loader 和 file-loader,這兩個庫不只能夠處理圖片,還有其餘的功能,有興趣的能夠自行學習。

先安裝庫

npm i --save-dev url-loader file-loader

建立一個 images 文件夾,放入兩張圖片,而且在 app 文件夾下建立一個 js 文件處理圖片
,目前的文件夾結構如圖

 

// addImage.js let smallImg = document.createElement('img') // 必須 require 進來 smallImg.src = require('../images/small.jpeg') document.body.appendChild(smallImg) let bigImg = document.createElement('img') bigImg.src = require('../images/big.jpeg') document.body.appendChild(bigImg)

接下來修改 webpack.config.js 代碼

module.exports = { // ... module: { rules: [ // ... { // 圖片格式正則 test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, use: [ { loader: 'url-loader', // 配置 url-loader 的可選項 options: { // 限制 圖片大小 10000B,小於限制會將圖片轉換爲 base64格式 limit: 10000, // 超出限制,建立的文件格式 // build/images/[圖片名].[hash].[圖片格式] name: 'images/[name].[hash].[ext]' } } ] } ] } }

運行 npm run start,打包成功以下圖

 

能夠發現大的圖片被單獨提取了出來,小的圖片打包進了 bundle.js 中。

在瀏覽器中打開 HTML 文件,發現小圖確實顯示出來了,可是卻沒有看到大圖,打開開發者工具欄,能夠發現咱們大圖的圖片路徑是有問題的,因此咱們又要修改 webpack.config.js 代碼了。

module.exports = { entry: './app/index.js', // 入口文件 output: { path: path.resolve(__dirname, 'build'), // 必須使用絕對地址,輸出文件夾 filename: "bundle.js", // 打包後輸出文件的文件名 publicPath: 'build/' // 知道如何尋找資源 } // ... }

最後運行下 npm run start,編譯成功了,再次刷新下頁面,能夠發現此次大圖被正確的顯示了。下一小節咱們將介紹如何處理 CSS 文件。

處理 CSS 文件

添加 styles 文件夾,新增 addImage.css 文件,而後在該文件中新增代碼

img { border: 5px black solid; } .test {border: 5px black solid;}

這一小節咱們先使用 css-loader 和 style-loader 庫。前者可讓 CSS 文件也支持 impost,而且會解析 CSS 文件,後者能夠將解析出來的 CSS 經過標籤的形式插入到 HTML 中,因此後面依賴前者。

npm i --save-dev css-loader style-loader

首先修改 addImage.js 文件

import '../styles/addImage.css' let smallImg = document.createElement('img') smallImg.src = require('../images/small.jpeg') document.body.appendChild(smallImg) // let bigImg = document.createElement('img') // bigImg.src = require('../images/big.jpeg') // document.body.appendChild(bigImg)

而後修改 webpack.config.js 代碼

module.exports = { // ... module: { rules: [ { test: /\.css$/, use: ['style-loader', { loader: 'css-loader', options: { modules: true } } ] }, ] } }

運行下 npm run start,而後刷新頁面,能夠發現圖片被正確的加上了邊框,如今咱們來看一下 HTML 的文件結構

 

 

從上圖能夠看到,咱們在 addImage.css 文件中寫的代碼被加入到了 style 標籤中,而且由於咱們開啓了 CSS 模塊化的選項,因此 .test 被轉成了惟一的哈希值,這樣就解決了 CSS 的變量名重複問題。

可是將 CSS 代碼整合進 JS 文件也是有弊端的,大量的 CSS 代碼會形成 JS 文件的大小變大,操做 DOM 也會形成性能上的問題,因此接下來咱們將使用 extract-text-webpack-plugin插件將 CSS 文件打包爲一個單獨文件

首先安裝 npm i --save-dev extract-text-webpack-plugin

而後修改 webpack.config.js 代碼

const ExtractTextPlugin = require("extract-text-webpack-plugin") module.exports = { // .... module: { rules: [ { test: /\.css$/, // 寫法和以前基本一致 loader: ExtractTextPlugin.extract({ // 必須這樣寫,不然會報錯 fallback: 'style-loader', use: [{ loader: 'css-loader', options: { modules: true } }] }) ] } ] }, // 插件列表 plugins: [ // 輸出的文件路徑 new ExtractTextPlugin("css/[name].[hash].css") ] }

運行下 npm run start,能夠發現 CSS 文件被單獨打包出來了

 

可是這時候刷新頁面會發現圖片的邊框消失了,那是由於咱們的 HTML 文件沒有引用新的 CSS 文件,因此這裏須要咱們手動引入下,在下面的章節咱們會經過插件的方式自動引入新的文件。

接下來,會用一個項目來繼續咱們的 webpack 學習,在這以前,先 clone 一下項目。該項目原地址是 這裏,由於使用的 webpack 版本過低,而且依賴的庫也有點問題,故我將項目拷貝了過來並修改了幾個庫的版本號。

請依次按照如下代碼操做

git clone https://github.com/KieSun/webpack-demo.git cd webpack-demo // 切換到 0.1 標籤上並建立一個新分支 git checkout -b demo 0.1 // 查看分支是否爲 demo,沒問題的話就能夠進行下一步 gst

如何在項目中使用 webpack

項目中已經配置了很簡單的 babel 和 webpack,直接運行 npm run start 便可

 

 

這時候你會發現這個 bundle.js 竟然有這麼大,這確定是不能接受的,因此接下來章節的主要目的就是將單個文件拆分爲多個文件,優化項目。

分離代碼

先讓咱們考慮下緩存機制。對於代碼中依賴的庫不多會去主動升級版本,可是咱們本身的代碼卻每時每刻都在變動,因此咱們能夠考慮將依賴的庫和本身的代碼分割開來,這樣用戶在下一次使用應用時就能夠儘可能避免重複下載沒有變動的代碼,那麼既然要將依賴代碼提取出來,咱們須要變動下入口和出口的部分代碼。

// 這是 packet.json 中 dependencies 下的 const VENOR = ["faker", "lodash", "react", "react-dom", "react-input-range", "react-redux", "redux", "redux-form", "redux-thunk" ] module.exports = { // 以前咱們都是使用了單文件入口 // entry 同時也支持多文件入口,如今咱們有兩個入口 // 一個是咱們本身的代碼,一個是依賴庫的代碼 entry: { // bundle 和 vendor 都是本身隨便取名的,會映射到 [name] 中 bundle: './src/index.js', vendor: VENOR }, output: { path: path.join(__dirname, 'dist'), filename: '[name].js' }, // ... }

如今咱們 build 一下,看看是否有驚喜出現


 

真的有驚喜。。爲何 bundle 文件大小壓根沒變。這是由於 bundle 中也引入了依賴庫的代碼,剛纔的步驟並無抽取 bundle 中引入的代碼,接下來讓咱們學習如何將共同的代碼抽取出來。

抽取共同代碼

在這小節咱們使用 webpack 自帶的插件 CommonsChunkPlugin

module.exports = { //... output: { path: path.join(__dirname, 'dist'), // 既然咱們但願緩存生效,就應該每次在更改代碼之後修改文件名 // [chunkhash]會自動根據文件是否更改而更換哈希 filename: '[name].[chunkhash].js' }, plugins: [ new webpack.optimize.CommonsChunkPlugin({ // vendor 的意義和以前相同 // manifest文件是將每次打包都會更改的東西單獨提取出來,保證沒有更改的代碼無需從新打包,這樣能夠加快打包速度 names: ['vendor', 'manifest'], // 配合 manifest 文件使用 minChunks: Infinity }) ] };

當咱們從新 build 之後,會發現 bundle 文件很明顯的減少了體積

 

可是咱們使用哈希來保證緩存的同時會發現每次 build 都會生成不同的文件,這時候咱們引入另外一個插件來幫助咱們刪除不須要的文件。

npm install --save-dev clean-webpack-plugin

而後修改配置文件

module.exports = { //... plugins: [ // 只刪除 dist 文件夾下的 bundle 和 manifest 文件 new CleanWebpackPlugin(['dist/bundle.*.js','dist/manifest.*.js'], { // 打印 log verbose: true, // 刪除文件 dry: false }), ] };

而後 build 的時候會發現以上文件被刪除了。

由於咱們如今將文件已經打包成三個 JS 了,之後也許會更多,每次新增 JS 文件咱們都須要手動在 HTML 中新增標籤,如今咱們能夠經過一個插件來自動完成這個功能。

npm install html-webpack-plugin --save-dev

而後修改配置文件

module.exports = { //... plugins: [ // 咱們這裏將以前的 HTML 文件當作模板 // 注意在以前 HTML 文件中請務必刪除以前引入的 JS 文件 new HtmlWebpackPlugin({ template: 'index.html' }) ] };

執行 build 操做會發現同時生成了 HTML 文件,而且已經自動引入了 JS 文件

 

按需加載代碼

在這一小節咱們將學習如何按需加載代碼,在這以前的 vendor 入口我發現忘記加入 router 這個庫了,你們能夠加入這個庫而且從新 build 下,會發現 bundle 只有不到 300KB 了。

如今咱們的 bundle 文件包含了咱們所有的本身代碼。可是當用戶訪問咱們的首頁時,其實咱們根本無需讓用戶加載除了首頁之外的代碼,這個優化咱們能夠經過路由的異步加載來完成。

如今修改 src/router.js

// 注意在最新版的 V4路由版本中,更改了按需加載的方式,若是安裝了 V4版,能夠自行前往官網學習 import React from 'react'; import { Router, Route, IndexRoute, hashHistory } from 'react-router'; import Home from './components/Home'; import ArtistMain from './components/artists/ArtistMain'; const rootRoute = { component: Home, path: '/', indexRoute: { component: ArtistMain }, childRoutes: [ { path: 'artists/new', getComponent(location, cb) { System.import('./components/artists/ArtistCreate') .then(module => cb(null, module.default)) } }, { path: 'artists/:id/edit', getComponent(location, cb) { System.import('./components/artists/ArtistEdit') .then(module => cb(null, module.default)) } }, { path: 'artists/:id', getComponent(location, cb) { System.import('./components/artists/ArtistDetail') .then(module => cb(null, module.default)) } } ] } const Routes = () => { return ( <Router history={hashHistory} routes={rootRoute} /> ); }; export default Routes;

而後執行 build 命令,能夠發現咱們的 bundle 文件又瘦身了,而且新增了幾個文件

 

 

將 HTML 文件在瀏覽器中打開,當點擊路由跳轉時,能夠在開發者工具中的 Network 一欄中看到加載了一個 JS 文件。

首頁

 

點擊右上角 Random Artist 之後

 

自動刷新

每次更新代碼都須要執行依次 build,而且還要等上一會很麻煩,這一小節介紹如何使用自動刷新的功能。

首先安裝插件

npm i --save-dev webpack-dev-server

而後修改 packet.json 文件

"scripts": { "build": "webpack", "dev": "webpack-dev-server --open" },

如今直接執行 npm run dev 能夠發現瀏覽器自動打開了一個空的頁面,而且在命令行中也多了新的輸出

 

等待編譯完成之後,修改 JS 或者 CSS 文件,能夠發現 webpack 自動幫咱們完成了編譯,而且只更新了須要更新的代碼

 

可是每次從新刷新頁面對於 debug 來講很不友好,這時候就須要用到模塊熱替換了。可是由於項目中使用了 React,而且 Vue 或者其餘框架都有本身的一套 hot-loader,因此這裏就略過了,有興趣的能夠本身學習下。

生成生產環境代碼

如今咱們能夠將以前所學和一些新加的插件整合在一塊兒,build 生產環境代碼。

npm i --save-dev url-loader optimize-css-assets-webpack-plugin file-loader extract-text-webpack-plugin

修改 webpack 配置

var webpack = require('webpack'); var path = require('path'); var HtmlWebpackPlugin = require('html-webpack-plugin') var CleanWebpackPlugin = require('clean-webpack-plugin') var ExtractTextPlugin = require('extract-text-webpack-plugin') var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') const VENOR = ["faker", "lodash", "react", "react-dom", "react-input-range", "react-redux", "redux", "redux-form", "redux-thunk", "react-router" ] module.exports = { entry: { bundle: './src/index.js', vendor: VENOR }, // 若是想修改 webpack-dev-server 配置,在這個對象裏面修改 devServer: { port: 8081 }, output: { path: path.join(__dirname, 'dist'), filename: '[name].[chunkhash].js' }, module: { rules: [{ test: /\.js$/, use: 'babel-loader' }, { test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, use: [{ loader: 'url-loader', options: { limit: 10000, name: 'images/[name].[hash:7].[ext]' } }] }, { test: /\.css$/, loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use: [{ // 這邊其實還可使用 postcss 先處理下 CSS 代碼 loader: 'css-loader' }] }) }, ] }, plugins: [ new webpack.optimize.CommonsChunkPlugin({ name: ['vendor', 'manifest'], minChunks: Infinity }), new CleanWebpackPlugin(['dist/*.js'], { verbose: true, dry: false }), new HtmlWebpackPlugin({ template: 'index.html' }), // 生成全局變量 new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("process.env.NODE_ENV") }), // 分離 CSS 代碼 new ExtractTextPlugin("css/[name].[contenthash].css"), // 壓縮提取出的 CSS,並解決ExtractTextPlugin分離出的 JS 重複問題 new OptimizeCSSPlugin({ cssProcessorOptions: { safe: true } }), // 壓縮 JS 代碼 new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } }) ] };

修改 packet.json 文件

"scripts": { "build": "NODE_ENV=production webpack -p", "dev": "webpack-dev-server --open" }

執行 npm run build

 

能夠看到咱們在經歷了這麼多步之後,將 bundle 縮小到了只有 27.1KB,像 vendor 這種經常使用的庫咱們通常可使用 CDN 的方式外鏈進來。

補充

webpack 配置上有些實用的小點在上文沒有提到,統一在這裏提一下。

module.exports = { resolve: { // 文件擴展名,寫明之後就不須要每一個文件寫後綴 extensions: ['.js', '.css', '.json'], // 路徑別名,好比這裏可使用 css 指向 static/css 路徑 alias: { '@': resolve('src'), 'css': resolve('static/css') } }, // 生成 source-map,用於打斷點,這裏有好幾個選項 devtool: '#cheap-module-eval-source-map', }
相關文章
相關標籤/搜索