一個現代化的webpack工程初建

specialCoder發表於2018-06-19

一 前言

文章介紹了一個現代化的專案的webpack4環境是什麼樣的。這裡只是介紹了基礎的功能,如果需要詳細的相關只是可以去webpack官網去查閱。
程式碼地址:github
環境特點:
1.使用了webpack-dev-middleware,在檔案內容更改之後自動編譯;
2.使用了webpack-hot-middleware,在熱編譯之後會自動重新整理頁面更改的內容,而不是重新整理整個頁面
3.使用了server.js檔案來自己控制啟動http服務,後期可以擴充套件簡單的後端功能

文章將webpack的配置檔案寫成了三份:公用部分檔案、開發環境檔案、線上環境檔案,具體在文章中會有詳細的介紹。

注意:文章介紹很詳細,適合新手入門使用,請耐心閱讀。

二 正文

1.path相關內容

文件:http://nodejs.cn/api/path.html
我們經常用到的就是path.resolve和path.join,那麼我們就講一下兩者的用法和區別:
相關文章:Difference between path.resolve and path.join invocation?

(1)path.join
path.join() 方法使用平臺特定的分隔符把全部給定的 path 片段連線到一起,並規範化生成的路徑。

path.join('/a', '/b') // Outputs '/a/b'
path.join('/foo', 'bar', 'baz/asdf', 'quux', '..'); // outputs '/foo/bar/baz/asdf'

summary:path.join通常用到的是簡單的將字串進行拼接。
(2)path.resolve
path.resolve() 方法會把一個路徑或路徑片段的序列解析為一個絕對路徑
給定的路徑的序列是從右往左被處理的,後面每個 path 被依次解析,直到構造完成一個絕對路徑(就會停止解析)

例如,給定的路徑片段的序列為:/foo、/bar、baz,則呼叫 path.resolve('/foo', '/bar', 'baz') 會返回 /bar/baz。

如果處理完全部給定的 path 片段後還未生成一個絕對路徑,則當前工作目錄會被用上。
生成的路徑是規範化後的,且末尾的斜槓會被刪除,除非路徑被解析為根目錄。
長度為零的 path 片段會被忽略。
如果沒有傳入 path 片段,則 path.resolve() 會返回當前工作目錄的絕對路徑。
例子:

path.resolve('/foo/bar', './baz');
// 返回: '/foo/bar/baz'

path.resolve('/foo/bar', '/tmp/file/');
// 返回: '/tmp/file

(3)work directory 與 dirname
工作目錄和檔案目錄並不是一直相等,我們以'./src/view/index.js'檔案為例:
檔案目錄是固定的,就是檔案所在的目錄:./src/view/index.js
工作目錄是不確定的,檢視當前所在目錄:pwd

如果你是在./src/view/ 下執行的index.js,那麼就和檔案路徑相同./src/view/index.js
但是如果你在./src下執行的index.js,那工作路徑則是./src

2.從server.js談起

我們要知道server.js都做了些什麼:

1.開啟一個http伺服器
2.使用熱編譯中介軟體,實時編譯修改過的內容
3.使用熱替換,實時檢視最新的頁面UI
4.【擴充套件】可以做一些後端的東西

程式碼如下:

const http = require('http');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackHotMiddleware = require('webpack-hot-middleware');

// 使用express啟用一個伺服器
const express = require('express');

// 引用開發環境下的webpack配置檔案
const config = require('./webpack.dev');
const app = express();
const webpackConfig = webpack(config);
const devMiddlewareCompiler = webpackDevMiddleware(webpackConfig,{
    publicPath:config.output.publicPath
});
const hotMiddlewareCompiler = webpackHotMiddleware(webpackConfig,{
    log: false,
    heartbeat: 2000,
 })

app.use(devMiddlewareCompiler);// 使用熱編譯中介軟體
app.use(hotMiddlewareCompiler);// 使用熱替換中介軟體

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

注意:為什麼要把熱編譯的功能放在node裡面呢?如果引用wepack-dev-server會自動管理熱編譯,它的原理也還是利用express開啟了一個小型伺服器,只不過我們看不到它。所以如果你要自己控制,並且想簡單方便的在後端做點小東西,可以完全使用上面的方法。如果是後端比較重就不建議這麼寫了,你需要開啟兩臺服務,透過代理的方式模擬進行前後端通訊了。

3.webpack common

webpack.common,js配置了無論是開發還是釋出都需要的東西,比如一些loader的轉譯,程式碼的打包壓縮等。
具體程式碼如下:

const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: {
    app: './src/index.js' // 入口檔案index.js
  },
  module:{
    rules:[{
        test:/\.css$/,
        use:[
          'style-loader',
          'css-loader'
        ],
    },
    {
       test: /\.(png|svg|jpg|gif)$/,
       use: [
         'file-loader'
       ]
     },
      {
           test: /\.(woff|woff2|eot|ttf|otf)$/,
           use: [
             'file-loader'
           ]
      }
  ],
},
  plugins: [
    new CleanWebpackPlugin(['dist']),
    new HtmlWebpackPlugin({
      title: 'Production'
    }),
  ],
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist'),
  }
};

下面講一講主要做了什麼事:

3-1 file-loader

a. 載入圖片
如果我們要想像引用模組那樣引用一個圖片,例如:

import Rose from './img/rose.jpg'

在 webpack 裡負責圖片翻譯的是 file-loader。而且,webpack 在最終構建時,會自動將模組中引用的圖片複製到相應目錄。如果你檢查此元素,你將看到實際的檔名已更改為像 5c999da72346a995e7e2718865d019c8.png 一樣。這意味著 webpack 在 src 資料夾中找到我們的檔案,併成功處理過它!

b. 載入字型
css檔案中引用字型:

 @font-face {
   font-family: 'MyFont';
  src:  url('./my-font.woff2') format('woff2'),
         url('./my-font.woff') format('woff');
   font-weight: 600;
   font-style: normal;
 }

【擴充套件】file-loader與url-loader
如果我們希望在頁面引入圖片(包括img的src和background的url)。當我們基於webpack進行開發時,引入圖片會遇到一些問題。其中一個就是引用路徑的問題。拿background樣式用url引入背景圖來說,我們都知道,webpack最終會將各個模組打包成一個檔案,因此我們樣式中的url路徑是相對入口html頁面的,而不是相對於原始css檔案所在的路徑的。這就會導致圖片引入失敗。這個問題是用file-loader解決的,file-loader可以解析專案中的url引入(不僅限於css),根據我們的配置,將圖片複製到相應的路徑,再根據我們的配置,修改打包後檔案引用路徑,使之指向正確的檔案。另外,如果圖片較多,會發很多http請求,會降低頁面效能。這個問題可以透過url-loader解決。url-loader會將引入的圖片編碼,生成dataURl。相當於把圖片資料翻譯成一串字元。再把這串字元打包到檔案中,最終只需要引入這個檔案就能訪問圖片了。當然,如果圖片較大,編碼會消耗效能。因此url-loader提供了一個limit引數,小於limit位元組的檔案會被轉為DataURl,大於limit的還會使用file-loader進行copy。

//url-loader封裝了file-loader。url-loader不依賴於file-loader,即使用url-loader時,只需要安裝url-loader即可
// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpg|gif)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 8192
            }
          }
        ]
      }
    ]
  }
}

3-2 css loader與style-loader
如果你在JS中使用:

import './index.css'

我們需要 CSS 載入器:

(1) css-loader - 預處理 CSS 檔案
(2) style-loader - 將 CSS 插入到 DOM 中的 style 標籤

要檢視 webpack 做了什麼,請檢查頁面(不要檢視頁面原始碼,因為它不會顯示結果),並檢視頁面的 head 標籤。它應該包含我們在 index.js 中匯入的 style 塊元素:<style>內容</style>。

注意:
(1)載入器的順序是從後往前的,loader 的順序很重要:如果把 style-loader 放到 css-loader 後面,我們就會撞見錯誤。
(2)我們如果只使用了 css-loader,則 webpack 只是將 CSS 檔案預處理成模組然後打包到構建檔案中,並不會插入到頁面。

【擴充套件】將CSS單獨打包:
webpack1/2/3:extract-text-webpack-plugin
webpack4:mini-css-extract-plugin

3-3 htm-lwebpack-plugin
詳細介紹見:html-webpack-plugin npm
這是一個webpack外掛,可以簡化建立HTML檔案以便為webpack包提供服務。 這對於webpack包來說特別有用,它在檔名中包含一個hash,用於更改每個編譯。 你可以讓外掛為你生成一個HTML檔案,使用lodash模板提供你自己的模板或使用你自己的載入器。

3-4 clean-webpack-plugin
詳細介紹見:clean-wenpack-plugin
一個在建立之前清除你build資料夾的webpack外掛。在打包生成新的build檔案的時候清除之前生成的,這非常有用。

4.webpack development

兩種環境的配置在webpack4中都支援mode的配置:development/production,具體的預設配置查詢可以移步這裡:webpack4 Mode的預設設定

webpack.dev.config.js:

 const webpack = require('webpack');
 const merge = require('webpack-merge');
 const common = require('./webpack.common.js');

 module.exports = merge(common, {
    mode:'development',
    plugins:[
      new webpack.NamedModulesPlugin(),
      new webpack.HotModuleReplacementPlugin(),
   ]
 });

這裡做了兩件事:
(1)mode:'development':定義環境為開發環境。在webpack4之後省了很多操作,只需要指定為開發環境,就會自動設定source map等資訊
(2)HotModuleReplacementPlugin:模組熱替換
使用熱替換需要兩步:

首先: Add the following plugins to the plugins array


plugins: [
    // OccurenceOrderPlugin is needed for webpack 1.x only
    new webpack.optimize.OccurenceOrderPlugin(),
    new webpack.HotModuleReplacementPlugin(),
    // Use NoErrorsPlugin for webpack 1.x
    new webpack.NoEmitOnErrorsPlugin()
]
Occurence ensures consistent build hashes, hot module replacement is somewhat self-explanatory, no errors is used to handle errors more cleanly.

其次:Add 'webpack-hot-middleware/client' into the entry array.

This connects to the server to receive notifications when the bundle rebuilds and then updates your client bundle accordingly.

5.webpack production

兩種環境的配置在webpack4中都支援mode的配置:development/production,具體的預設配置查詢可以移步這裡:webpack4 Mode的預設設定

 const webpack = require('webpack');
 const path = require('path');
 const merge = require('webpack-merge');
//  const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
 const common = require('./webpack.common.js');


 module.exports = merge(common, {
     output:{
        // publicPath:path.resolve(__dirname, 'dist'),
     },
     mode:'production',
 });

指定環境為生產環境,預設開啟UglifyJSPlugin。

【擴充套件】將檔案標記為無副作用(side-effect-free)
src/math.js:

export function square(x) {
  return x * x;
}

export function cube(x) {
  return x * x * x;
}

src/index.js:

import { cube } from './math.js';

math.js檔案的square函式沒有被匯入,但是,它仍然被包含在 bundle 中。
在一個純粹的 ESM 模組世界中,識別出哪些檔案有副作用很簡單。然而,我們的專案無法達到這種純度,所以,此時有必要向 webpack 的 compiler 提供提示哪些程式碼是“純粹部分”。
這種方式是透過 package.json 的 "sideEffects" 屬性來實現的。

{
  "name": "your-project",
  "sideEffects": false
}

如同上面提到的,如果所有程式碼都不包含副作用,我們就可以簡單地將該屬性標記為 false,來告知 webpack,它可以安全地刪除未用到的 export 匯出。
如果你的程式碼確實有一些副作用,那麼可以改為提供一個陣列:

{
  "name": "your-project",
  "sideEffects": [
    "./src/some-side-effectful-file.js"
  ]
}

三 後記

相關文章