一步步学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...

系列文章

相关文章
相关标签/搜索