如何學習配置webpack(一)

有酒窩的程式設計師發表於2019-04-25

專案小白如何從0開始配置webpack

自己配置過webpack的人應該都知道,webpack真的好複雜,一開始做專案都是拿別人現成的做做小修改,但是別人的終究沒有自己配的舒服。所以我打算寫這篇文章,從我的配置中帶大家瞭解webpack配置,簡化讀webpack官方頁面的複雜度。

首先我們需要明確我們要求webpack做什麼?

1.所寫即所得,我們在編譯器中寫了程式碼能馬上呈現在偵錯程式上(熱更新服務)

2.本地開發獲取資料存在的跨域問題(代理,解決跨域)

3.使用es678,less,sass等(翻譯,讓瀏覽器懂得我們的程式碼)

4.提高專案效能,比如壓縮程式碼,壓縮圖片等(專案優化)

5.如果與輸入相關的需求,找entry(比如多頁面就有多個入口) 6.如果與輸出相關的需求,找output(比如你需要定義輸出檔案的路徑、名字等等) 7.如果與模組定址相關的需求,找resolve(比如定義別名alias) 8.如果與轉譯相關的需求,找loader(比如處理sass處理es678N) 9.如果與構建流程相關的需求,找plugin(比如我需要在打包完成後,將打包好的檔案複製到某個目錄,然後提交到git上) 接下來我們來看下webpack的一些基本配置

1.entry(專案入口)

2.output(出口檔案)

3.modules(模組處理)

4.plugin(外掛)

5.resolve

6.devserver

7.mode,

8devtool

1.專案入口設定(entry)

主要有三種方式

1.字串形式

entry: '.src/main.js'
複製程式碼

2.陣列形式

entry: [react, react-dom]
複製程式碼

3.物件形式

entry: {
    main:'./src/index2.js',
    second: './src/index2.js',
    vendor:['react', 'react-dom']
}
複製程式碼

2.出口檔案設定及(output)

const path = require('path');
module.exports = {
  entry: './path/to/my/entry/file.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'my-first-webpack.bundle.js'
  }
};
複製程式碼

在上面的示例中,我們通過 output.filename 和 output.path 屬性,來告訴 webpack bundle 的名稱,以及我們想要 bundle 生成(emit)到哪裡。 Path.resolve是什麼,引入的path模組是幹什麼用的

Nodejs

該path.resolve()方法將一系列路徑或路徑段解析為絕對路徑。

給定的路徑序列從右到左處理,隨後每個path路徑都被預先載入,直到構造出絕對路徑。例如,給定路徑段的序列:/foo,/bar,baz,呼叫path.resolve('/foo', '/bar', 'baz')將返回/bar/baz。

如果在處理path完所有給定段之後尚未生成絕對路徑,則使用當前工作目錄。 生成的路徑已規範化,並且除非將路徑解析為根目錄,否則將刪除尾部斜槓。 零長度path段被忽略。

如果沒有path傳遞段,path.resolve()將返回當前工作目錄的絕對路徑。

path.resolve('/foo/bar', './baz');
// Returns: '/foo/bar/baz'
path.resolve('/foo/bar', '/tmp/file/');
// Returns: '/tmp/file'
path.resolve('wwwroot', 'static_files/png/', '../gif/image.gif');
// If the current working directory is /home/myself/node,
// this returns '/home/myself/node/wwwroot/static_files/gif/image.gif'
_dirname
複製程式碼

當前模組的目錄名稱。這是一樣 path.dirname()的__filename。

示例:node example.js從中執行/Users/mjr

console.log(__dirname);
// Prints: /Users/mjr
console.log(path.dirname(__filename));
// Prints: /Users/mjr
複製程式碼

我們在配置output是常用的幾個

filename 這個是輸出檔案的名稱,字串型別,如果只有一個輸出檔案,可以寫成靜態名稱。例如

output:{
     filename:'bundle.js'
}
複製程式碼

當然了,在我們日常工作中,一般情況下是不會有這種情況的,當專案很大的時候,如果不分塊打包,bundle.js會驚人的大,專案越大,bundle.js就會越大,這不是我們今天討論的重點,以後再說

多個chunk的時候怎麼辦呢

webpack會為每個生成的Chunk取一個名稱,Chunk的名稱和Entry的配置有關:

1. 如果entry是一個string或者array,就只會生成一個chunk,這個chunk的名稱是main;

2. 如果entry是一個object,就可能出現多個chunk,這時chunk的名稱是object鍵值對裡鍵的名稱

然而,當通過多個入口起點(entry point)、程式碼拆分(code splitting)或各種外掛(plugin)建立多個

bundle,應該使用以下一種替換方式,來賦予每個 bundle 一個唯一的名稱…… 使用入口名稱:

output:{
     filename: "[name].bundle.js"
}
複製程式碼

使用內部 chunk id

output:{
    filename: "[id].bundle.js"
}
複製程式碼

使用每次構建過程中,唯一的 hash 生成

output:{
filename: "[name].[hash].bundle.js"
}
複製程式碼

使用基於每個 chunk 內容的 hash:

output:{
filename: "[chunkhash].bundle.js"
}
複製程式碼

這裡多出來幾個陌生詞彙hash、chunkhash,它們是什麼? hash、chunkhash和contenthash三者的區別 hash

hash的值是相同的,如果都使用hash的話,因為這是工程級別的,即每次修改任何一個檔案,所有檔名的hash至都將改變。所以一旦修改了任何一個檔案,整個專案的檔案快取都將失效。所以對於沒有改變的模組而言,這樣做顯然不恰當,因為快取失效了嘛。此時,chunkhash的用途隨之而來。 chunkhash

只有被修改了的檔案的檔名,hash值修改

filename: '[name]-[chunkhash].js'

當我們使用mini-css-extract-plugin拆分css的時候,就需要使用chunkhash,我一個js檔案裡面引入了css檔案。這時要是我修改了js,但沒修改css,可以通過chunkhash快取css檔案

contenthash

對css使用了chunkhash之後,我們測試會發現,如果修改了js,css檔名的hash值確實沒變,但這時要是我們修改css檔案的話,我們就會發現css檔名的chunkhash值居然沒變化,這樣就導致我們的非覆蓋釋出css檔案失效了。所以這裡需要注意就是css檔案必須使用contenthash。

上面介紹的 id、name、hash、chunkhash等都是webpack內建變數, id是唯一標示,不會重複,從0開始, name 是模組名稱,是你自己起的,在配置路由懶載入的時候可以自己命名 官網介紹的很清楚,我就不再這裡囉嗦了, chunkFilename

官網解釋:此選項決定了非入口(non-entry) chunk 檔案的名稱, 什麼場景需要呢?

在按需載入(非同步)模組的時候,也就是路由懶載入,這樣的檔案是沒有被列在entry中的,

比如

{
    entry: {
        "index": "pages/index.jsx"
    },
    output: {
         filename: "[name].min.js",
        chunkFilename: "[name].min.js"
    }
}
const myModel = r => require.ensure([], () => r(require('./myVue.vue')), 'myModel')
複製程式碼

上面的例子,通過filename輸出的是index.min.js 非同步載入的模組是要以檔案形式載入哦,所以這時生成的檔名是以chunkname配置的,通過chunkFilename輸出的是myModel.min.js 所以chunkFilename也很重要哦!!! path path是配置輸出檔案存放在本地的目錄,字串型別,是絕對路徑

output:{
    path: path.resolve(__dirname, 'dist/assets')
}
複製程式碼

__dirname,這個昨天說過,可以回顧一下,就是當前檔案所在的資料夾的名字 publicPath 對構建出的資源進行非同步載入(圖片,檔案),該選項的值是以 runtime(執行時) 或 loader(載入時) 所建立的每個 URL 為字首。因此,在多數情況下,此選項的值都會以/結束。 預設值是一個空字串 "",即相對路徑,配置錯誤會導致404 簡單說,就是靜態檔案託管在cdn上 舉個栗子: 如果你這麼配置:

output:{
    filename:'[name]_[chunkhash:8].js',
    publicPath:'https://www.qdtalk.com/assets/'
}
複製程式碼

打包編譯後,html頁面就是這樣的

path 和publicPath都支援字串模板

  • 配置 單出口 // webpack 配置
const path = require('path');

module.exports = {
  entry: main: './src/main.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  }
};
複製程式碼
  • 配置 多出口
// webpack 配置
const path = require('path');

module.exports = {
  entry: {
    app: './src/app.js',
    vendors: './src/vendors.js'
  },
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist')
  }
};
複製程式碼

3.modules(模組處理)

模組處理主要是對loader的配置

此處引用官網對loader的定義

loader 讓 webpack 能夠去處理那些非 JavaScript 檔案(webpack 自身只理解 JavaScript)。loader 可以將所有型別的檔案轉換為webpack能夠處理的有效模組,然後你就可以利用webpack的打包能力,對它們進行處理。

本質上,webpack loader 將所有型別的檔案,轉換為應用程式的依賴圖(和最終的 bundle)可以直接引用的模組。 注意,loader 能夠 import 匯入任何型別的模組(例如 .css 檔案),這是 webpack 特有的功能,其他打包程式或任務執行器的可能並不支援。我們認為這種語言擴充套件是有很必要的,因為這可以使開發人員建立出更準確的依賴關係圖。 在更高層面,在 webpack 的配置中 loader 有兩個目標:

1. test 屬性,用於標識出應該被對應的 loader 進行轉換的某個或某些檔案。

2. use 屬性,表示進行轉換時,應該使用哪個 loader。

webpack.config.js
const path = require('path');
const config = {
  output: {
    filename: 'my-first-webpack.bundle.js'
  },
  module: {
    rules: [
      { test: /\.txt$/, use: 'raw-loader' }
    ]
  }
};
module.exports = config;
複製程式碼

以上配置中,對一個單獨的 module 物件定義了 rules 屬性,裡面包含兩個必須屬性:test 和 use。這告訴 webpack 編譯器(compiler) 如下資訊:

“嘿,webpack 編譯器,當你碰到「在 require()/import 語句中被解析為 '.txt' 的路徑」時,在你對它打包之前,先使用 raw-loader 轉換一下。” 重要的是要記得,在 webpack 配置中定義 loader 時,要定義在 module.rules 中,而不是 rules。然而,在定義錯誤時 webpack 會給出嚴重的警告。為了使你受益於此,如果沒有按照正確方式去做,webpack 會“給出嚴重的警告”

在webpack中有許許多多的loader,此處我按照官方文件解釋下和我理解的用法 loader 特性

  • 幾乎所有 loader 都 需要安裝, 但 不需要 在 webpack 配置檔案中通過 require 引入
  • 逆向編譯,鏈式傳遞 檔案
  • raw-loader 載入檔案原始內容(utf-8)
  • val-loader 將程式碼作為模組執行,並將 exports 轉為 JS 程式碼
  • url-loader 像 file loader 一樣工作,但如果檔案小於限制,可以返回 data URL
  • file-loader 將檔案傳送到輸出資料夾,並返回(相對)URL raw-loader 可以讓你在程式碼中引入檔案
import txt from './file.txt';
複製程式碼

val-loader 載入的模組必須使用以下函式介面,將 default export 匯出為一個函式。

function answer () {
  return {
    code: 'module.exports = 42;'
  }
};
module.exports = answer;
複製程式碼

url-loader

url-loader 功能類似於 file-loader,但是在檔案大小(單位 byte)低於指定的限制時,可以返回一個 DataURL。 file-loader 將檔案傳送到輸出資料夾,並返回(相對)URL(不會再對檔案做處理) JSON

  • json-loader 載入 JSON 檔案(預設包含)
  • json5-loader 載入和轉譯 JSON 5 檔案
  • cson-loader 載入和轉譯 CSON 檔案 主要處理json檔案

轉換編譯(Transpiling)

  • script-loader 在全域性上下文中執行一次 JavaScript 檔案(如在 script 標籤),不需要解析
  • babel-loader 載入 ES2015+ 程式碼,然後使用 Babel 轉譯為 ES5
  • buble-loader 使用 Bublé 載入 ES2015+ 程式碼,並且將程式碼轉譯為 ES5
  • traceur-loader 載入 ES2015+ 程式碼,然後使用 Traceur 轉譯為 ES5
  • ts-loader 或 awesome-typescript-loader 像 JavaScript 一樣載入 TypeScript 2.0+
  • coffee-loader 像 JavaScript 一樣載入 CoffeeScript

React開發過程中我們需要將jsx或者es6程式碼轉譯成es5程式碼。 我們需要用到babel-loader

{
'test': /\.(js|jsx)$/, // babel 轉換為相容性的 js
'exclude': /node_modules/,
'loader': 'babel-loader',
'query': {
'presets': ['react', 'latest', 'stage-0', 'react-hmre']
},
'include': path.resolve(__dirname, '../client')
},
複製程式碼

注:exclude是不轉換node_modules的程式碼,query === options 此處注意babel版本6和7設定上有區別。 如果引用錯誤會報錯 模板(Templating)

  • html-loader 匯出 HTML 為字串,需要引用靜態資源
  • pug-loader 載入 Pug 模板並返回一個函式
  • jade-loader 載入 Jade 模板並返回一個函式
  • markdown-loader 將 Markdown 轉譯為 HTML
  • react-markdown-loader 使用 markdown-parse parser(解析器) 將 Markdown 編譯為 React 元件
  • posthtml-loader 使用 PostHTML 載入並轉換 HTML 檔案
  • handlebars-loader 將 Handlebars 轉移為 HTML
  • markup-inline-loader 將內聯的 SVG/MathML 檔案轉換為 HTML。在應用於圖示字型,或將 CSS 動畫應用於 SVG 時非常有用。

樣式

  • style-loader 將模組的匯出作為樣式新增到 DOM 中
  • css-loader 解析 CSS 檔案後,使用 import 載入,並且返回 CSS 程式碼
  • less-loader 載入和轉譯 LESS 檔案
  • sass-loader 載入和轉譯 SASS/SCSS 檔案
  • postcss-loader 使用 PostCSS 載入和轉譯 CSS/SSS 檔案
  • stylus-loader 載入和轉譯 Stylus 檔案 如果我們在開發中使用了less或者sass,我們需要先轉換成css在引入到專案中
{
'test': /\.less$/,
'loader': ['style-loader', 'css-loader', {
'loader': 'less-loader',
'options': {
'javascriptEnabled': true
}
}]
},
複製程式碼

此處的執行順序。less-loader -> css-loader -> style-loader 清理和測試(Linting && Testing)

  • mocha-loader 使用 mocha 測試(瀏覽器/NodeJS)
  • eslint-loader PreLoader,使用 ESLint 清理程式碼
  • jshint-loader PreLoader,使用 JSHint 清理程式碼
  • jscs-loader PreLoader,使用 JSCS 檢查程式碼樣式
  • coverjs-loader PreLoader,使用 CoverJS 確定測試覆蓋率

框架(Frameworks)

  • vue-loader 載入和轉譯 Vue 元件
  • polymer-loader 使用選擇前處理器(preprocessor)處理,並且 require() 類似一等模組(first-class)的 Web 元件
  • angular2-template-loader 載入和轉譯 Angular 元件

核心重點(敲黑板)

plugins(外掛)

作用:可以處理各種任務,從打包優化和壓縮,一直到重新定義環境中的變數 loader不需要require. plugin需要

loader 被用於轉換某些型別的模組,而外掛則可以用於執行範圍更廣的任務。外掛的範圍包括,從打包優化和壓縮,一直到重新定義環境中的變數。外掛介面功能極其強大,可以用來處理各種各樣的任務。

想要使用一個外掛,你只需要 require() 它,然後把它新增到 plugins 陣列中。多數外掛可以通過選項(option)自定義。你也可以在一個配置檔案中因為不同目的而多次使用同一個外掛,這時需要通過使用 new 操作符來建立它的一個例項。 下面是我配置的用於開發環境的plugins例項

webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin'); // 通過 npm 安裝
const webpack = require('webpack'); // 用於訪問內建外掛
const config = {
  module: {
    rules: [
      { test: /\.txt$/, use: 'raw-loader' }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({template: './src/index.html'})
  ]
};
module.exports = config;
複製程式碼
'plugins': [
new webpack.optimize.OccurrenceOrderPlugin(), // 調整模組的打包順序,用到次數更多的會出現在檔案的前面
new webpack.DefinePlugin({ // DefinePlugin 允許建立一個在編譯時可以配置的全域性常量。
'process.env.NODE_ENV': JSON.stringify('development')
}),
new HtmlWebpackPlugin({ // HtmlWebpackPlugin簡化了HTML檔案的建立,以便為你的webpack包提供服務。這對於在檔名中包含每次會隨著編譯而發生變化雜湊的 webpack bundle 尤其有用。 你可以讓外掛為你生成一個HTML檔案,使用lodash模板提供你自己的模板,或使用你自己的loader。
'hash': true,
'title': 'Demo',
'filename': 'index.html',
'template': path.resolve(__dirname, '../views/index.ejs'),
'inject': 'body'
}),
new webpack.HotModuleReplacementPlugin(), // 啟用熱更新
new webpack.NoEmitOnErrorsPlugin(), // 輸出階段遇到編譯錯誤跳過
new webpack.NamedModulesPlugin(), // 當開啟 HMR 的時候使用該外掛會顯示模組的相對路徑,建議用於開發環境。
new webpack.ProgressPlugin(), // 輸出構建進度
]
複製程式碼

相關文章