All Articles

如何利用webpack来提升前端开发效率(一)?

业务场景分析

随着Vue、React生态的蓬勃发展,我们在开发单页应用时,一般已离不开利用脚手架自动生成项目模板,熟悉的有 create-react-app project vue create project等命令。 但在很多应用场景下,我们往往需要走出前端框架的“温室”来进行开发,常见的有:

  • H5单页面制作
  • 传统网页开发(以JQueryBootstrap为代表,以适应低版本浏览器如 IE)
  • 展示为主导的静态页面
  • 优化点

    可能大家在遇到上述开发场景时,可能就二话不说,直接开启编辑器,撸起袖子就是“代码一把梭”。
    不妨静下心来,仔细剖析,一般流程我们有以下几个优化点:

  • 代码变更,页面自动刷新(不必再疯狂蹂躏F5)
  • 自动处理静态资源的优化,缓存,打包,压缩(减少资源加载时间,减少资源HTTP请求)
  • 用上 ES6,ES7,Sass,PostCSS…(什么浏览器厂商前缀,import加载,轻松实现)
  • 开发和生产两套配置(开发和生产职责分明,易于维护)

    解决方案

    webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle
    通俗的来说,webpack就像一个大厨,HTML是原料,而图片、CSS、JS则是调味料,我们只需提供原料和调味料,webpack 就会以“高超的厨艺”帮我们,烹饪出一道佳肴,也就是构建我们的Web应用程序

    初尝webpack

    下面让我们从一个例子入手,利用webpack4.x逐步构建一个高复用,功能俱全,模块清晰的前端脚手架。

在命令行输入以下命令

mkdir my-webpack && cd my-webpack
npm init -y // 可以 npm i nrm 下载 nrm 使用 nrm use taobeo 切换淘宝镜像,提升下载速度,nrm ls 查看当前使用的npm源
npm i webpack webpack-cli -g // 先全局安装,为了可以使用webpack命令
npm i webpack webpack-cli -D // 再项目本地安装,因为项目所需webpack版本是固定的,而全局webpack版本会随时更新,为了防止版本冲突

创建webpack.config.js、src/index.js、dist/index.html

<!-- dist/index.html -->
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>My Webpack</title>
</head>

<body>
  <script src="bundle.js"></script>
</body>

</html>
// src/index.js
console.log('hello webpack!');
// webpack.config.js
const path = require('path');
const pathResolve = targetPath => path.resolve(__dirname, targetPath)

module.exports = {
  entry: { 
    index: pathResolve('src/index.js') // 入口文件,即要打包的js文件
  },
  output: {
    path: pathResolve('dist'), // 打包文件的位置
    filename: 'bundle.js' // 打包文件的名称
  },
}

然后在当前目录下,在命令行输入webpack 可以看到

Hash: c949bc1db5e801df578d
Version: webpack 4.28.4
Time: 377ms
Built at: 2019-01-15 20:57:39
    Asset       Size  Chunks             Chunk Names
bundle.js  959 bytes       0  [emitted]  index
Entrypoint index = bundle.js
[0] ./src/index.js 47 bytes {0} [built]

WARNING in configuration // 通过设置mode的值('development' or 'production')即可解除警告
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/

这表明我们打包成功,dist文件夹生成了bundle.jsindex.html使用。打开dist/index.html,如图所示: 有人不禁要问,这webpack也不过如此,只不过把js文件换了个名字(index -> bundle),反而更麻烦,还不如按部就班敲代码。No,这才是第一步,接来下可要坐稳了。

添加loader和plugin

我们发现,dist/index.html还要手写<script>脚本引入,实在麻烦,何不由webpack自动生成,再来加点佐料(CSS,图片),岂不是更完美!我们删除dist文件夹,在src路径下添加一张你喜欢的图片。

在命令行运行以下命令。

npm i html-webpack-plugin style-loader css-loader url-loader file-loader -D

修改以下文件

/* src/index.css */
body {
  background: #ff4040;
}
// src/index.js
import './index.css'; // 导入css文件
import icon from './leaf.png'; 导入图片url

console.log('hello webpack!');

// 创建img标签给src赋值,并插入html文档
const img = document.createElement('img');
img.setAttribute('src', icon);
document.body.appendChild(img);
// webpack.config.js
const path = require('path');
const pathResolve = (targetPath) => path.resolve(__dirname, targetPath);
const htmlWebpackPlugin = require('html-webpack-plugin'); // 导入‘html-webpack-plugin’插件
module.exports = {
  entry: {
    index: pathResolve('src/index.js'),
  },
  output: {
    path: pathResolve('dist'),
    filename: '[name].js',
  },
  module: {  // loader专注于处理制定格式的文件,来加载各种各样的资源
    rules: [
      {
        test: /\.css$/, // 正则匹配以.css结尾的文件
        use: ['style-loader', 'css-loader'], // loader从右向左执行 先使用css-loader提取css资源,然后使用style-laoder通过<style>标签插入<head>
      },
      {
        test: /\.(png|jpg|jpeg|svg|gif)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
                limit: 8192, // 小于8kb的文件则转为dataURL:Base64形式,大于8kb则交由file-loader处理,生成新图片并打包
                name: '[name].[hash:7].[ext]', // hash值对应本次build的hash值,每次打包都不相同
                outputPath: 'img', // 输出路径,即dist/img
            },
          },
        ],
      },
    ],
  },
  plugins: [ // 插件专注于处理webpack在编译过程中某个特定的任务
    new htmlWebpackPlugin({ // 该插件可以自动生成HTML文件,并导入静态资源
      filename: pathResolve('dist/index.html'), // 生成HTML文件名称
      title: 'my-webpack' // 文档名称
    }),
  ],
};

在命令行运行webpack,可以看到自动生成了dist文件夹,打开dist文件夹下的index.html,可以看到:

添加devServer和热更新

当我们每次修改代码时,都要输入webpack命令重新构建,令人头痛,有没有简单的办法呢?当然有! 我们可以利用webpack-dev-server来为我们搭建一个本地服务器,提供及时浏览和热更新。

在命令行输入以下命令

npm i webpack-dev-server -D

修改以下文件

// package.json
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
  + "dev" : "webpack-dev-server --mode development",
  + "build": "webpack --mode production"
  },
// webpack.config.js
+   const webpack = require('webpack');
    module.exports = {
    +   devServer: {
            contentBase: pathResolve('dist'), // 本地服务器所加载文件的目录
            port: '8080', // 设置端口号为8080
            inline: true, // 文件修改后实时刷新(浏览器刷新)
            historyApiFallback: true, // 使用HTML5 History API时,对于任何404响应,返回index.html
            hot: true // 热更新(浏览器不刷新)
        },
        plugins: [
        +    new webpack.HotModuleReplacementPlugin(), // 热更新插件 
        +    new webpack.NamedModulesPlugin()
      ]
 }
// src/index.js
// ...
if (module.hot) {
  module.hot.accept() // 如果存在热更新,则启用
}

执行npm run dev

修改src/index.js

// src/index.js
+ console.log('hot replacement!');

可以立刻看到:

很明显,此处网页并未刷新,而是发生了热更新。

使用Sass,postCSS,分离CSS

回过头看dist下的文件,并没有任何css相关文件,html文件中页没有style标签,原来经过打包后,css被整合在js中。 显然这并不是我们想要的,我们下一步的任务是将CSS从中分离出来,并且尝试使用Sass(一种CSS预处理器语言)和postCSS来添加浏览器厂商前缀 。

在命令行输入以下命令

npm i mini-css-extract-plugin node-sass sass-loader postcss-loader autoprefixer -D

新增src/index.scss

/* src/index.scss */
$link-color: #f1f1f1;

body {
  background: $link_color;
}

#avatar {
  width: 200px;
  height: 200px;
  background: url('./packet.png'); // 准备一张大于8kb的图片
  border-radius: 50%;
}

新增src/index.html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>my-webpack</title>
</head>

<body>
  <img id="avatar"></img>
</body>

</html>

新增postcss.config.js

module.exports = {
  plugins: [
    require('autoprefixer')({ // 自动管理浏览器前缀的插件
      browsers: ['last 10 versions', 'Firefox >= 20', 'Android >= 4.0', 'iOS >= 8']
    })
  ]
}

修改以下文件

// src/index.js
- import './index.css';
+ import './index.scss';
// webpack.config.js
+   const MiniCssExtractPlugin = require('mini-css-extract-plugin');
    module.exports = {
        module:{
            rules:[
        +       {
                    test: /\.(sa|sc|c)ss$/,
                    use: [{
                    loader: MiniCssExtractPlugin.loader,
                    options: {
                        publicPath: '../' // CSS中最终路径 = publicPath + background: url,设置publicPath确保图片取得到
                        }
                    },
                        'css-loader',
                        'postcss-loader',
                        'sass-loader',
                    ],
                },
            ]
        },
        plugins: [
        +    new MiniCssExtractPlugin({
                filename: 'css/[name].[contenthash:7].css', // contenthash 根据css文件内容生成hash值
            }),
            new htmlWebpackPlugin({
            -    title: 'my-webpack', 
            +    template: pathResolve('src/index.html'), // HTML模板
            }),
        ],
    },
  ],

运行npm run build,可以看到,CSS文件已被分离成独立文件,在head头部以link方式引入,并且自动添加了webkit moz ms等兼容性厂商前缀。

结语

第一篇主要研究了什么是webpack以及loader、plugin的用法,这样我们的前端脚手架已经可以处理基本的业务需求。
第二篇将对我们的前端脚手架做进一步的优化,包括但不限于代码分割,压缩,缓存处理,多页面
本人水平有限,不足之处还希望大家在评论区提出,😃😃😃再次感谢大家的阅读!!!

已更新 如何利用webpack来提升前端开发效率(二)?