一步步学Webpack4(1)-- 开发环境搭建入门_weixin_33862041的博客-程序员秘密

技术标签: ViewUI  webpack  开发工具  前端  

开发工具诞生的目的永远是加速开发. 程序员应该不断追求更快更好的开发工具.

前文一步步学Webpack4(0)-- 实战起步已经完成了Webpack环境的搭建以及实现了一句命令自动打包项目,这一次我们继续使用之前的项目webpack-stepbystep来尝试搭建适合对开发者友好的项目开发环境.

本章按照以下步骤进行:

  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的人大概都有一颗想给自己写个顺手的手脚架的心吧,吃自己的狗粮这件事对开发者肯定是好事,但是前提是自己真正懂得自己的需求.

对于一个普通前端开发者来说,一个简单项目的手脚架必须具备一定的能力,总结一下一些必不可少的需求吧:

  1. 方便的调试信息追溯;
  2. 代码修改之后自动打包;
  3. 代码修改之后自动更新页面内容;

接下来我们就来借助Webpack的能力,一个个实现这些需求~

2. 练习1:Webpack开发工具认识与选择

2.1 source map 实现调试信息追溯

文章跟随 一步步学Webpack4(0)-- 实战起步 继续开发.

项目已经能够使用Webpack打包了,我们现在使用这个项目来随便写点会发生错误的代码,例如在方法第一行加入 console.abg('generate component')

index.js

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 完成打包,运行结果如下:

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带来的便利.

  • 步骤一、安装loader到项目
npm i -D style-loader css-loader
  • 步骤二、加入样式对应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()
    ]
};
  • 步骤三、在项目的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);
    });
}
  • 步骤四、在终端输入命令 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...

系列文章

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_33862041/article/details/88716032

智能推荐

判断101-200之间有多少个素数,并输出所有素数。 只能被1和它本身整除的自然数为素数(质数)_毕富国的博客-程序员秘密

判断101-200之间有多少个素数,并输出所有素数。只能被1和它本身整除的自然数为素数(质数)public class Prime { public void prime(int m,int n) { int count=0; for(;m<n;m++) { boolean isPrime=true; for(int i=2;i<Math.sqrt(m);...

OpenMP在ARM-Linux以及NDK中的编译和使用_Tommy_wxie的博客-程序员秘密

以前对OpenCV在ARM-Linux, ARM-Android上的优化做了很多编译方面的努力,例如添加TBB支持,添加CUDA支持(Nvidia K1平台上)。这次突然听同事说增加了OpenMP选项后,在Windows+X86上有极大的优势,adaboost速度提高3倍。所以赶快在ARM-Android-NDK上测试一下。  0. OpenMP基础:OpenMP(

系统蓝屏的几种姿势,确定不了解下么?_BianChengNan-BCN-BCN的博客-程序员秘密

前言在 蓝屏(BSOD)转储设置,看本文就够了! 这篇文章里比较详细的介绍了蓝屏转储设置。做好设置后,我们就可以在需要的时候使系统蓝屏了。这样我们就可以拿到一份系统转储,供我们分析问题了。本文介绍几种可以使系统蓝屏的办法。当然肯定还有其它办法,如果哪位小伙伴儿知道比较实用的方法,欢迎留言分享。几种蓝屏方法1、通过驱动思路是:在内核执行有问题的代码(比如,在驱动的入口处加上简单的 int*...

VUE报错 [Vue warn]: Invalid prop: type check failed for prop “value“. Expected String, N_三人行,必有我师!!!的博客-程序员秘密

场景:用了iview的select option 出现下图报错原因是:列表返回的某个code是空修改:注意:value="val.code+''" <Select v-model="val.sendCode"> <Option v-for='(val, idx) in val.datalibrary' :key="idx" :value="val.code+''">{{val.name}}</Option&g..

修改Linux服务器中的MySQL数据库密码,详细!!_linux mysql修改密码_javafg的博客-程序员秘密

知道mysql的密码情况下通过登录mysql系统,mysql -uroot -pEnter password: 【输入原来的密码】mysql>use mysql;查看下自己mysql的版本,注意不同的版本修改密码的方式会有所不同mysql>select version();根据自己的版本从下面的方式中,选择一种进行修改密码即可5.7以前mysql>update user set password=password(“123456”) where user=‘root’;

《数据库系统概念》学习笔记——第六章 形式化关系查询语言_关系代数表达式是唯一的吗_RaymondLove~的博客-程序员秘密

Table of Contents第六章 形式化关系查询语言6.1 关系代数6.1.1 基本运算6.1.2 关系代数的形式化定义6.1.3 附加的关系代数运算6.1.4 扩展的关系代数运算6.2 元组关系推演6.2.1 查询示例6.2.2 形式化定义6.2.3 表达式的安全性6.2.4 语言的表达能力6.3 域关系运算6.3.1 形式化定义6.3.2 查询的例子6.3.3 表达式的安全性6.3.4 语言的表达能力总结第六章 形式化关系查

随便推点

扇区http://www.360doc.com/content/12/1021/10/1317564_242731836.shtml_扬子居的博客-程序员秘密

http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">http://www.w3.org/1999/xhtml">                            www.360doc.com" name="author" />                引导扇区

Android Handler sendEmptyMessageDelayed 和 sendEmptyMessage 的使用_胡小牧的博客-程序员秘密

sendEmptyMessageDelayed 一般我们做延时操作的时候使用到比如现在有一个延时10跳转的demo 如下public class MainActivity extends AppCompatActivity implements Handler.Callback { private Handler handler; @Override pr...

app里嵌套h5 调试 vconsole_app中嵌套了前端代码怎么看报错信息_Mr_wuying的博客-程序员秘密

在app里嵌套H5页面 我们看不到H5页面的报错信息 所有调试会很困难这里用到了vconsole.js引用<script src="https://cdn.bootcss.com/vConsole/3.3.4/vconsole.min.js"></script>在js 中先初始化<script> // 初始化 var vConsole = new VConsole(); console.log('Hello world'); //如果写deb

AndroidMPChart——BarChart_android barchart_瘋小書的博客-程序员秘密

AndroidMPChart的获取依赖:Project 的build.gradle文件中添加allprojects {    repositories {        maven { url 'https://jitpack.io' }    }}然后在 module中的build,gradle 中添加implementation 'com.github.Phi...

VC6.0 error LNK2001: unresolved external symbol [email protected]_weixin_30466953的博客-程序员秘密

--------------------Configuration: oxToint1 - Win32 Debug--------------------Linking...main.obj : error LNK2001: unresolved external symbol [email protected]/oxToint1.exe : fatal error LNK1120: 1 ...

阿里某员工面试华为后吐槽:面试官太水,反问几句都答不上来_Java和Android架构的博客-程序员秘密

热文导读|点击标题阅读互联网寒冬下,程序员如何突围提升自己?Android中高级面试题准备整理分享(含答案)从流水线工人,到谷歌上班的程序媛,一位湖南娄底妹子的励志...

推荐文章

热门文章

相关标签