一步步學Webpack4(1)-- 開發環境搭建入門

開發工具誕生的目的永遠是加速開發. 程序員應該不斷追求更快更好的開發工具.

前文一步步學Webpack4(0)-- 實戰起步已經完成了Webpack環境的搭建以及實現了一句命令自動打包項目,這一次咱們繼續使用以前的項目webpack-stepbystep來嘗試搭建適合對開發者友好的項目開發環境.css

本章按照如下步驟進行:html

  1. 開發需求總結:跟隨官方文檔 Development ,總結前端開發者對於調試與開發的需求;
  2. 練習1:Webpack開發工具認識與選擇;
  3. 練習2:Webpack熱模塊更新(HMR);
  4. 練習3:認識loader並完成基礎配置;

寫Webpack文章不寫版本都是耍流氓,這篇文章基於當下最新的 webpack v4.22.0 以及 webpack-cli v3.1.2 編寫.前端

1. 開發需求探索

Eating your own dog food

嘗試深刻探索學習Webpack的人大概都有一顆想給本身寫個順手的手腳架的心吧,吃本身的狗糧這件事對開發者確定是好事,可是前提是本身真正懂得本身的需求.webpack

對於一個普通前端開發者來講,一個簡單項目的手腳架必須具有必定的能力,總結一下一些必不可少的需求吧:git

  1. 方便的調試信息追溯;
  2. 代碼修改以後自動打包;
  3. 代碼修改以後自動更新頁面內容;

接下來咱們就來藉助Webpack的能力,一個個實現這些需求~程序員

2. 練習1:Webpack開發工具認識與選擇

2.1 source map 實現調試信息追溯

文章跟隨 一步步學Webpack4(0)-- 實戰起步 繼續開發.github

項目已經可以使用Webpack打包了,咱們如今使用這個項目來隨便寫點會發生錯誤的代碼,例如在方法第一行加入 console.abg('generate component')web

index.jsnpm

import _ from 'lodash';
function component () {
    console.abg('generate component');
    let element = document.createElement('div');
    element.innerHTML = _.join(['Hello', 'Webpack'], ' ');
    return element;
}
document.body.appendChild(component());

而後在終端中運行 webpack 完成打包,運行結果以下:json

Error1

發現錯誤是被指向了編譯後的文件 main.js ,這並非咱們想要的. 發生錯誤的時候瀏覽器若是不能追溯到源代碼發生錯誤的位置,這將增大調試的難度,幸虧Webpack已經提供解決這個問題的方法--source map,咱們只須要簡單地修改一下配置文件:

webpack.config.js

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

module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'main.js',
        path: path.resolve(__dirname, 'dist')
    },
    devtool: 'inline-source-map',
    plugins: [
        new CleanWebpackPlugin(['dist']),
        new HtmlWebpackPlugin({
            inject: false,
            template: 'index.html',
            filename: 'index.html'
        })
    ]
};

上面代碼中加入了一句 devtool: 'inline-source-map' , 從新在終端中運行 webpack 完成打包,運行結果以下:

Error2

成功了,藉助source map的力量, 錯誤發生時瀏覽器從 main.js 追溯到了源代碼 index.js 中. 至此咱們已經成功實現了第一個需求「方便的錯誤信息追溯」. 另外要特別注意的是,source map 只能在開發環境中使用以方便調試,千萬不能用於生產環境,簡單緣由看看添加了 source map以後的main.js文件大小就知道了(逃

固然 source map 還有許多配置能夠選擇, 不過與本章的學習關係不大, 先繼續往下學習吧~

2.2 試用開發工具

刀耕火種時期每次保存完代碼都要F5,在項目中應用了Webpack以後每次保存完代碼竟然須要先Webpack打包再F5,這麼愚蠢的事情程序員怎麼可能容許呢,因而開發工具們開始誕生了:

2.2.1 Webpack觀察者模式(webpack's Watch Mode)

嚴格來講這不算是一種額外的開發工具,這只是Webpack的一種運行模式,能夠在終端輸入 webpack --watch 開始持續監聽文件變化,只要修改代碼並保存,webpack將會自動幫你打包項目,聽起來還不錯可以自動打包,可是這種模式並不能幫助開發者更新頁面內容也就是說, 你仍是須要本身按F5刷新..., 感受仍是有點慘啊.

算了 =。= Next one

2.2.2 webpack-dev-server(推薦)

這是一個官方推薦的新手友好的開發工具,webpack-dev-server提供了一個具有實時重載功能的簡單服務器,只須要下載到工程中,並對配置文件進行簡單配置便可使用,安裝命令以下:

npm i -D webpack-dev-server

此處基本使用 webpack-dev-server 的默認配置,惟一修改的地方是配置開啓服務器的位置即 contentBase: './dist',整份配置以下所示:

webpack.config.js

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

module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'main.js',
        path: path.resolve(__dirname, 'dist')
    },
    devtool: 'inline-source-map',
    devServer: {
        contentBase: './dist'
    },
    plugins: [
        new CleanWebpackPlugin(['dist']),
        new HtmlWebpackPlugin({
            inject: false,
            template: 'index.html',
            filename: 'index.html'
        })
    ]
};

接下來打開 package.json 文件,在"scripts"中添加一行運行腳本 "start": "webpakc-dev-server --open",完整package.json文件以下:

package.json

{
  "name": "webpack-stepbystep",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "scripts": {
    "start": "webpack-dev-server --open"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "clean-webpack-plugin": "^0.1.19",
    "html-webpack-plugin": "^3.2.0",
    "webpack": "^4.22.0",
    "webpack-cli": "^3.1.2",
    "webpack-dev-server": "^3.1.10"
  },
  "dependencies": {
    "lodash": "^4.17.11"
  }
}

而後打開終端,運行命令 npm start,稍後就能看到瀏覽器自動打開了 localhost:8080 界面,若是對代碼進行保存修改,web服務器就會自動從新打包代碼並將更新應用到瀏覽器網頁上~至此前端程序員的對於手腳架的幾個需求已經徹底獲得了知足,如今已經能夠舒舒服服地開始前端開發了~

至此項目提交爲 feat(project): add source map & devtools .

P.S. 官方文檔中還有一個開發工具 webpack-dev-middleware, 與Node.js結合可以進行更多的自定義配置,不過暫時咱們不須要用到它.

3. 練習2:Webpack熱模塊更新(HMR)

3.1 修改錯誤代碼測試

完成上一小節的配置以後,咱們能夠開始嘗試在當前項目中編寫代碼了,首先咱們固然是先來改正第一小節的錯誤,將 index.js 中的console.abg('generate component'); 改成 console.log('generate component'); ,文件完整代碼以下所示:

index.js

import _ from 'lodash';
function component () {
    console.log('generate component');
    let element = document.createElement('div');
    element.innerHTML = _.join(['Hello', 'Webpack'], ' ');
    return element;
}
document.body.appendChild(component());

保存以後,很快就能看到頁面上出現了熟悉的 Hello Webpack~ 修改代碼以後只須要保存,剩下的事情Webpack都會幫你自動搞定,自動更新的效果不錯嘛~

然而,這只是一個小小的項目。設想一下,你如今正在調試一個規模比較大的項目,在最後一步按下"提交button"以前,你忽然想起"提交button"綁定錯了觸發的事件. 若是此時修改代碼並保存,應用頁面將會被刷新,也就是說你剛剛選擇的許多選項的狀態會被重置回初始值。你的粗心讓你須要把以前的選擇流程走一遍,若是以後又發現了另外一個小錯誤那麼又要再走一遍流程...此時的你多麼但願有一個工具能讓你保持着頁面當前的狀態,並偷偷地幫你更新修改好的綁定關係, 你只須要在完成更新後從容按下"提交button"就完事. 沒錯,這就是這一小節的重點 模塊熱更新 Hot Module Replacement(HMR)!

3.2 實戰:簡單模塊熱更新(HMR)

說了那麼多,不如show me your code. 好,如今立刻經過實戰來見識一下 HMR 的厲害.

3.2.1 實戰準備

咱們新建一個模塊稱爲printMe, 負責打印一段文字, 在index.js中引用該模塊併爲編寫一個button來觸發它,完整代碼以下所示:

print.js

export default function printMe () {
    console.log('Updating print.js');
}

index.js

import _ from 'lodash';
import printMe from './print';
function component () {
    let element = document.createElement('div');
    let btn = document.createElement('button');
    element.innerHTML = _.join(['Hello', 'Webpack'], ' ');
    btn.innerHTML = 'Click me and check the console.';
    btn.onclick = printMe;
    element.appendChild(btn);
    return element;
}
document.body.appendChild(component());

3.2.2 實時刷新測試

修改 print.js 的打印內容並保存,當前效果是:整個頁面直接經過刷新來更新界面.

3.2.3 應用模塊熱更新

  • 步驟一:首先更新webpack的配置文件. 在配置頭部加入對webpack的引用,而後在devServer對象中配置 hot: true 來開啓 HMR ,最後在plugins對象中配置 HotModuleReplacementPlugin 插件以替換模塊,完整代碼以下所示:

    webpack.config.js

    const path = require('path');
    const CleanWebpackPlugin = require('clean-webpack-plugin');
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    const webpack = require('webpack');
    
    module.exports = {
        entry: './src/index.js',
        output: {
            filename: 'main.js',
            path: path.resolve(__dirname, 'dist')
        },
        devtool: 'inline-source-map',
        devServer: {
            contentBase: './dist',
            hot: true
        },
        plugins: [
            new CleanWebpackPlugin(['dist']),
            new HtmlWebpackPlugin({
                inject: false,
                template: 'index.html',
                filename: 'index.html'
            }),
            new webpack.HotModuleReplacementPlugin()
        ]
    };
  • 步驟二:修改 index.js 文件,在底部加入 HMR 相關代碼,令其在 printMe 模塊發生改變時能夠接受更新的模塊,完整代碼以下所示:

    index.js

    import _ from 'lodash';
    import printMe from './print';
    function component () {
        let element = document.createElement('div');
        let btn = document.createElement('button');
        element.innerHTML = _.join(['Hello', 'Webpack'], ' ');
        btn.innerHTML = 'Click me and check the console.';
        btn.onclick = printMe;
        element.appendChild(btn);
        return element;
    }
    document.body.appendChild(component());
    
    if (module.hot) {
        module.hot.accept('./print.js', function () {
            console.log('Accepting the updated printMe module!');
            printMe();
        });
    }
  • 步驟三:修改 print.js 的打印文字並保存,經過觀察控制打印結果,發現頁面完成了修改而且沒有產生刷新.
  • 步驟四:你覺得就這樣結束了?其實並無,HMR 手擼的話仍是比較坑的. 點擊button你會發現控制檯中打印的東西一直都是最初始的打印值,這是由於button的事件依然綁定在舊的函數上,爲了解決這個問題,咱們將經過 index.js 底部 HMR 代碼更新button的事件綁定,具體完整代碼以下所示:

    index.js

    import _ from 'lodash';
    import printMe from './print';
    function component () {
        let element = document.createElement('div');
        let btn = document.createElement('button');
        element.innerHTML = _.join(['Hello', 'Webpack'], ' ');
        btn.innerHTML = 'Click me and check the console.';
        btn.onclick = printMe;
        element.appendChild(btn);
        return element;
    }
    // document.body.appendChild(component());
    let ele = component();
    document.body.appendChild(ele);
    
    if (module.hot) {
        module.hot.accept('./print.js', function () {
            console.log('Accepting the updated printMe module!');
            // printMe();
            document.body.removeChild(ele);
            ele = component();
            document.body.appendChild(ele);
        });
    }

如今再修改 print.js 的打印文字並保存,經過觀察控制打印結果,發現頁面完成了修改而且沒有產生刷新,而且點擊以後控制檯會出現新修改的文字. HMR 配置成功~

如今以爲 HMR 開發很難?Webpack的開發者天然考慮到了這一點,Webpack 的 loader將會幫你把這一個過程變得簡單.

3.3 HRM 修改樣式表

只需下載loader並完成配置,以後的同類型改動須要更新時,loader會自動在幕後經過 module.hot.accept 完成對於內容的修補.

此次實戰咱們先安裝並配置 styleloader & cssloader, 而後藉助loader的力量幫助咱們實現頁面樣式的模塊熱更新,體驗loader帶來的便利.

  • 步驟1、安裝loader到項目
npm i -D style-loader css-loader
  • 步驟2、加入樣式對應loader配置, 完整配置代碼以下所示:

webpack.config.js

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

module.exports = {
    entry: {
        app: './src/index.js'
    },
    output: {
        filename: 'main.js',
        path: path.resolve(__dirname, 'dist')
    },
    devtool: 'inline-source-map',
    devServer: {
        contentBase: './dist',
        hot: true
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: ['style-loader', 'css-loader']
            }
        ]
    },
    plugins: [
        new CleanWebpackPlugin(['dist']),
        new HtmlWebpackPlugin({
            inject: false,
            template: 'index.html',
            filename: 'index.html'
        }),
        new webpack.HotModuleReplacementPlugin()
    ]
};
  • 步驟3、在項目的src文件夾下添加文件 styles.css 並在 index.js 引用:

styles.css

body {
    background-color: blue;
}

index.js

import _ from 'lodash';
import printMe from './print';
import './styles.css';
function component () {
    let element = document.createElement('div');
    let btn = document.createElement('button');
    element.innerHTML = _.join(['Hello', 'Webpack'], ' ');
    btn.innerHTML = 'Click me and check the console.';
    btn.onclick = printMe;
    element.appendChild(btn);
    return element;
}
// document.body.appendChild(component());
let ele = component();
document.body.appendChild(ele);

if (module.hot) {
    module.hot.accept('./print.js', function () {
        console.log('Accepting the updated printMe module!');
        // printMe();
        document.body.removeChild(ele);
        ele = component();
        document.body.appendChild(ele);
    });
}
  • 步驟4、在終端輸入命令 npm start, 確認應用開啓完畢後,修改 styles.css, 保存後觀察控制檯的打印:
body {
    background-color: #fff;
}

發現頁面在沒刷新的狀況下完成了背景顏色的變化. 藉助loader的力量成功實現了 HMR 的效果.

3.4 HMR小結

Hot Module Replacement(HMR)是Webpack最棒的特性之一,當代碼修完並保存以後,Webpack將從新打包項目,並將新的模塊發送到瀏覽器端,瀏覽器更新對應的模塊,以此達到更新應用頁面的目的.

不一樣於實時刷新的開發工具庫,HMR 在更新以後依舊可以保持原有的應用狀態,提升了開發者的開發效率.

至此項目提交爲 feat(project): finish dev-server & HMR config .

4. 練習3:認識loader並完成基礎配置

在Webpack出現以前,前端工程師們使用的打包工具一般是 grunt 或者 gulp, 這些工具處理圖片等資源的方式一般是複製,也就是將文件複製一份到打包目錄下.

可是Webpack不一樣,它對於js和資源文件一視同仁,也就是將資源也看做模塊,使用到這些模塊的地方須要顯示調用資源,而後由Webpack動態構建依賴圖完成統一打包, Webpack經過資源間的強依賴關係,完美避開了隱式引用和無效引用形成的錯誤和浪費.

爲了完成對任何類型資源的引用,社區出現了各類格式的loader來幫助Webpack完成這個任務. 比較通用的loader有:

  1. 樣式loader: style-loader、css-loader等
  2. 圖片loader: file-loader
    Tip:進階能夠學習使用 pimage-webpack-loader](https://github.com/tcoopman/i... 或者 url-loader
  3. 字體loader: file-loader
  4. 數據loader

以上資源能夠直接放在一個控件目錄下,並經過顯式聲明依賴創建起該控件的依賴關係圖. 這樣的控件更具有可移植性.

具體使用操做能夠跟隨官方文檔的Asset Management章節跑一波,目標是認識經常使用loader並跟隨文檔完成當前項目配置便可.

至此項目提交爲feat(project): finish loaders study

5. 項目地址

6. 總結

本章以開發需求探索開始, 根據總結的需求提出解決方案並選擇新手友好的開發工具 webpack-dev-server , 接着進一步瞭解方便開發者調試修改應用特性 HMR, 最後再學習並使用loader完成項目的基礎配置. 簡單的開發環境搭建已經完成了,如今可使用這個環境試試愉快的代碼編寫吧~

To be continued...

系列文章

相關文章
相關標籤/搜索