Skip to content

前言

前端构建工具 Webpack 最近特火,火到 Vue/React 官方推出的脚手架都是基于 Webpck 打造的。

为了更了解 Webpack,特意实打实地安装配置 Webpack。对以后进阶学习也能夯实基础,现在一起学习入门级的 Webpack 吧!

认识 Webpack

先来观察应用 Webpack 能做的事:

webpack

从图中得出:Webpack 能打包所有 JS 脚本;能打包所有 style 样式;能打包所有图片;能打包所有预编译语言。通俗的理解就是能打包前端所有资源

安装 Webpack

首先确保你已经安装了 Node.jsGit。找到存放项目的目录,在该目录下初始化项目。在终端执行:

$ npm init
// 或者
$ npm init -y
$ npm init
// 或者
$ npm init -y

初始化后生成一个 package.json 文件,大致内容:

{
  "name": "webpack",
  "version": "1.0.0",
  "description": "study-webpack",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "study-webpack"
  ],
  "author": "yuan",
  "license": "ISC"
}
{
  "name": "webpack",
  "version": "1.0.0",
  "description": "study-webpack",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "study-webpack"
  ],
  "author": "yuan",
  "license": "ISC"
}

为了后续快速安装其他依赖,这里使用淘宝镜像。在终端执行:

$ npm install -g cnpm --registry=https://registry.npm.taobao.org
$ npm install -g cnpm --registry=https://registry.npm.taobao.org

接来下安装 Webpack,在 npm 官网查询安装手册。在终端执行:

$ cnpm install --save-dev webpack
// 或者
$ yarn add webpack --dev
$ cnpm install --save-dev webpack
// 或者
$ yarn add webpack --dev

附:使用 yarn 语法安装,确保已经安装 yarn

注意:最新版本 Webpackwebpack-cli 从中分离了出来需要单独安装。在终端执行:

$ cnpm i webpack-cli --save-dev
$ cnpm i webpack-cli --save-dev

安装完 Webpack 之后需要其运行起来,得需要一个配置文件,其名称为 webpack.config.js,不能为其他名称。如果是其他名称 Webpack 找不到该配置文件,就抛出错误提示。

运行 Webpack

查询官网手册后,填写 webpack.config.js 配置。对 entry 属性值和 filename 属性值进行简单修改,webpack.config.js 大致内容如下:

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js'
  }
};
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js'
  }
};

module.exports 导出一个对象,其中:

entry 表示打包资源入口,该字段属性值可以是 String / Array / Object

output 表示打包资源出口,也就是经打包的资源从该口输出。

distWebpack 打包完成后存放资源的目录。

配置完内容后,在根目录下创建目录 src,里面编写一个叫 index.js 脚本:

// index.js
document.write('Hello Webpack!');
// index.js
document.write('Hello Webpack!');

为了方便看效果,在根目录下创建一个 index.html 模版,并引入打包后的资源 bundle.js

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <title>入门webpack</title>
</head>
<body>

</body>
</html>
<script src="./dist/bundle.js"></script>
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <title>入门webpack</title>
</head>
<body>

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

使用预定义命令启动 Webpack,可以在 package.json 文件中的 scripts 字段中添加命令。

// ...
  "scripts": {
    "start": "webpack",
    "test": "echo \"Error: no test specified\" && exit 1"
  }
  // ...
// ...
  "scripts": {
    "start": "webpack",
    "test": "echo \"Error: no test specified\" && exit 1"
  }
  // ...

在终端执行 cnpm start 打包完成后会看到在根目录下生成 dist 目录,里面包含 bundle.js 脚本,在浏览器端运行 index.html 可以看到页面输出 Hello Webpack!。

到此完成了 Webpack 初步的打包。

执行 Webpack 打包时,终端执行输出一些信息:

Hash: 9d157b09dd8d37122dad
Version: webpack 4.42.1
Time: 560ms
Built at: 2020-04-19 11:48:36
    Asset       Size  Chunks             Chunk Names
bundle.js  961 bytes       0  [emitted]  main
Entrypoint main = bundle.js
[0] ./src/index.js 31 bytes {0} [built]
Hash: 9d157b09dd8d37122dad
Version: webpack 4.42.1
Time: 560ms
Built at: 2020-04-19 11:48:36
    Asset       Size  Chunks             Chunk Names
bundle.js  961 bytes       0  [emitted]  main
Entrypoint main = bundle.js
[0] ./src/index.js 31 bytes {0} [built]
  • Hash 表示当前文件打包生成的 hash 值,文件改变,hash 值就会变。

  • Version 表示项目当前安装 Webpack 的版本。

  • Time 表示项目打包所花费的时间。

  • Build 表示项目打包日期,打包生成文件名称和文件大小

  • Entrypoint 表示项目打包入口点。

即:

module.exports = {
  entry: './src/index.js',
  // ...
}

// 等价于
module.exports = {
  entry: {
    main: './src/index.js'
  },
  // ...
}
module.exports = {
  entry: './src/index.js',
  // ...
}

// 等价于
module.exports = {
  entry: {
    main: './src/index.js'
  },
  // ...
}
  • chunks: 打包文件的 id,现在只有一个 bundle.js 打包文件,有多个的时候,会有多个不同的 chunk

  • Chunk Names: 打包文件的名字。

最后一行表示打包生成的文件路径。

大多数网站中都会使用缓存,减少页面加载时长。

Webpack 打包也可以做到这点,把之前的 bundle.js 改成带有 hash 值。

修改后的 webpack.config.js

const path = require('path');
 
module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[hash].js' // hash
  }
};
const path = require('path');
 
module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[hash].js' // hash
  }
};

重新运行 cnpm start 打包完成后会看到根目录下生成 dist 目录,里面包含带 hash 值的资源。如果想缩短 hash 值,可以进行截取长度,比如:[hash:6]

如果想要优化打包后的资源和想使用 Webpack 强大的功能,请继续往下看。

认识 Plugin

pluginWebpack 的核心,Webpack 自身的多数功能都是用这个插件接口,让 Webpack 打包变得极其灵活。

经过认识 Webpack 初次打包后,发现每次执行 cnpm start 打包完成后都会在 dist 目录中追加打包生成后的新资源。造成 dist 文件很大。这时 clean-webpack-plugin 就可以登场,帮助我们解决这个问题。

npm 官网搜索该插件,点击名称进入详情查看安装手册,在终端执行:

$ cnpm i --save-dev clean-webpack-plugin
$ cnpm i --save-dev clean-webpack-plugin

webpack.config.js 中添加该配置:

// ...
const { CleanWebpackPlugin } =require('clean-webpack-plugin');
module.exports = {
  plugins: [
    new webpack.ProgressPlugin(),
    new CleanWebpackPlugin()
  ]
}
// ...
const { CleanWebpackPlugin } =require('clean-webpack-plugin');
module.exports = {
  plugins: [
    new webpack.ProgressPlugin(),
    new CleanWebpackPlugin()
  ]
}

在终端执行 cnpm start 会看到上一次打包生成的资源自动删除后,重新创建新的打包资源。

如果在项目中要引入打包后的资源,并且该资源带有 hash 值时不易方便使用,脚本太多也不易区分,这时可以使用 Webpack 提供的 HTML 模版插件解决问题。

npm 官网搜索该插件,点名称进去查看安装手册,在终端执行:

$ cnpm i --save-dev html-webpack-plugin
$ cnpm i --save-dev html-webpack-plugin

安装成功后,在根目录 package.json 中的 devDependencies 里能看到该插件和该插件的版本。

Webpack 中配置该插件:

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

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[hash:6].js'
  },
  plugins: [
    new HtmlWebpackPlugin() // htmlPlugin
  ]
};
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[hash:6].js'
  },
  plugins: [
    new HtmlWebpackPlugin() // htmlPlugin
  ]
};

在终端执行 cnpm start 会看到在根目录下生成 dist 目录,里面包含带 hash 值的资源和压缩过的 index.html

如果不想使用压缩过的资源,可以在 webpack.config.js 中进行配置:

// ...
module.exports = { 
  mode: "development",
// ...
}
// ...
module.exports = { 
  mode: "development",
// ...
}

根据 mode 参数 Webpack 会区分是生产环境还是开发环境。一般生成环境 mode 设置为 production,开发环境设置为 development

设置完成后,在终端执行 cnpm start 打包完成后,然后在浏览器上运行 index.html 可以看到输出内容没变化,页面代码没有压缩。

如果想对 src/index.html 做一些调整,比如:修改 title,创建多个模版文件,多个模版文件引入不同的脚本等等;只需要在 new HtmlWebpackPlugin() 中添加一些配置项就能解决。

比如修改 title

module.exports = {
  // ...
     new HtmlWebpackPlugin({
        title: '学习webpack'
     })
  // ...
 }
module.exports = {
  // ...
     new HtmlWebpackPlugin({
        title: '学习webpack'
     })
  // ...
 }

在终端执行 cnpm start 打包完成后,在浏览器上运行 index.html 会看到 title 的变化。

项目难免会美化页面,那么就得给页面添加一些样式,可以写在单独文件中,可以写在 .html 模版中,这时处理 CSS 可以使用 css-loader 解决问题。

认识 loader

loader 用于对模块的源代码进行转换。loader 可以在 import 或"加载"模块时预处理文件。可以将文件从不同的语言(如 TypeScript)转换为 JavaScript,或将内联图像转换为 data URLloader 甚至允许直接在 JavaScript 模块中 import CSS 文件。

①、处理 CSS

首先安装处理 CSS 相应的 loadercss-loaderstyle-loader

npm 官网搜索该 loader,点击名称进去查看安装手册,在终端执行:

$ cnpm i --save-dev css-loader style-loader
$ cnpm i --save-dev css-loader style-loader
  • css-loader 处理以 .css 后缀的文件。

  • style-loader 经过 css-loader 处理过的 CSS 插入到 DOM 中。

安装成功后,在根目录 package.json 中的 devDependencies 里能看到该 loader 和该 loader 的版本。

webpack.config.js 中添加处理 CSSloader 配置:

module.exports = {
  // ...
  module: {
      rules: [
        {
          test: /\.css$/,
          use: [ 
            { loader: "style-loader" },  
            { loader: "css-loader" } 
          ]
        }
      ]
  },
  // ...
}
module.exports = {
  // ...
  module: {
      rules: [
        {
          test: /\.css$/,
          use: [ 
            { loader: "style-loader" },  
            { loader: "css-loader" } 
          ]
        }
      ]
  },
  // ...
}

注意:use 选项顺序,先使用 css-loader 再使用 style-loader

项目中一般都是使用单独文件写入样式,这里使用以 .css 为后缀的文件负责控制页面样式:

*{ margin:0px;padding:0px;}
body{ background: red; }
*{ margin:0px;padding:0px;}
body{ background: red; }

在根目录 src/index.js 中引入该样式文件:

document.write('hello webpack') 
require('./style.css')
document.write('hello webpack') 
require('./style.css')

在终端执行 cnpm start 打包完成后,在浏览器上运行 index.html,能看到页面背景色变红色。使用开发者工具也能看到页面插入 style 标签,style 标签里面嵌入刚刚写的样式:

webpack

随着项目复杂度的提升,控制页面的样式也很多;如果按照这样写法,页面会有一大段来控制样式,考虑到对后期的性能优化不友好,可以考虑把样式单独打包一个文件。

②、提取 CSS

新版本 Webpack4.x 建议使用 mini-css-extract-plugin

npm 官网搜索该插件,点击名称进去查看安装手册,在终端执行:

$ cnpm install --save-dev mini-css-extract-plugin
$ cnpm install --save-dev mini-css-extract-plugin

安装完该插件后,在根目录 package.json 中的 devDependencies 里能看到该插件和该插件的版本。

webpack.config.js 中的 module 选项和 plugin 选项中配置该插件:

// ...
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  // ...
    module: {
      rules: [
        {
          test: /\.css$/,
          use: [ 
            { loader: "style-loader" },  
            {
                loader: MiniCssExtractPlugin.loader,
                options: {
                  esModule: true,
                },
            },
            'css-loader',
          ]
        }
      ]
  },
  plugins: [
      new HtmlWebpackPlugin({
        title: '学习webpack'
      }),
      new MiniCssExtractPlugin({ 
        filename: 'style.css'
      })
  ]
}
// ...
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  // ...
    module: {
      rules: [
        {
          test: /\.css$/,
          use: [ 
            { loader: "style-loader" },  
            {
                loader: MiniCssExtractPlugin.loader,
                options: {
                  esModule: true,
                },
            },
            'css-loader',
          ]
        }
      ]
  },
  plugins: [
      new HtmlWebpackPlugin({
        title: '学习webpack'
      }),
      new MiniCssExtractPlugin({ 
        filename: 'style.css'
      })
  ]
}

在终端执行 cnpm start 打包完成后,在浏览器上运行 index.html,可以看到与之前效果一样;使用开发者工具能看到生成的样式文件 style.css,之前嵌套在页面中的样式不见了。

效果如下:

webpack

大型项目中一般会选择应用预编译语言,这里使用 Sass 预编译语言。

nmp 官网搜索该 loader,点击名称进去查看使用安装手册,在终端执行:

$ cnpm install --save-dev sass-loader node-sass
$ cnpm install --save-dev sass-loader node-sass

安装完毕后,在根目录 package.json 中的 devDependencies 里能看到该 loader 和该 loader 的版本。

webpack.config.js 中的 module 选项中配置:

module.exports = {
  // ...
  module: {
    rules: [
      // ...
        {
            test: /\.s[ac]ss$/i,
            use: [
              // Creates `style` nodes from JS strings
              'style-loader',
              // Translates CSS into CommonJS
              'css-loader',
              // Compiles Sass to CSS
              'sass-loader',
            ],
        }
    ]
  }
  // ...
}
module.exports = {
  // ...
  module: {
    rules: [
      // ...
        {
            test: /\.s[ac]ss$/i,
            use: [
              // Creates `style` nodes from JS strings
              'style-loader',
              // Translates CSS into CommonJS
              'css-loader',
              // Compiles Sass to CSS
              'sass-loader',
            ],
        }
    ]
  }
  // ...
}

同样在根目录 src 下创建一个专门存放 sass 样式文件:

// index.scss
$fontSize: 16px;
body{ font-size:$fontSize;}
// index.scss
$fontSize: 16px;
body{ font-size:$fontSize;}

在根目录 src/index.js 中引入该 scss 文件:

document.write('hello webpack') 
require('./style.css')
require('./index.scss')
document.write('hello webpack') 
require('./style.css')
require('./index.scss')

在终端执行 cnpm start 打包完成后,在浏览器上运行 index.html,能看到页面字体变化。

③、打包图片

打包处理图片使用 url-loaderfile-loader

npm 官网搜索该 loader,点击名称进去查看安装手册,在终端执行:

$ cnpm install url-loader file-loader --save-dev
$ cnpm install url-loader file-loader --save-dev

安装完该插件后,在根目录 package.json 中的 devDependencies 里能看到该 loader 和该 loader 的版本。

webpack.config.js 中的 module 选项中配置:

module.exports = {
  // ...
    module: {
      rules: [
       // ...
        {
            test: /\.(png|jpe?g|gif)$/i,
            loader: 'file-loader',
            options: {
                outputPath: 'images',
                name: '[1]-[name].[ext]'
            },
        }
      ]
  },
  // ...
}
module.exports = {
  // ...
    module: {
      rules: [
       // ...
        {
            test: /\.(png|jpe?g|gif)$/i,
            loader: 'file-loader',
            options: {
                outputPath: 'images',
                name: '[1]-[name].[ext]'
            },
        }
      ]
  },
  // ...
}

style.css 样式文件中引入一张图片作为背景图:

*{ margin:0px;padding:0px;}
body{ background-color: red; }
body{ background: url('./webpack1-3.jpg') repeat-x; }
*{ margin:0px;padding:0px;}
body{ background-color: red; }
body{ background: url('./webpack1-3.jpg') repeat-x; }

在终端执行 cnpm start 打包完成后,在浏览器上运行 index.html,能看到之前的背景色被改变了。

技术不断创新,前端技术领域也是如此。新项目中越来越喜欢使用 ES6 作为处理 JS 页面数据交互,接下来继续打包 ES6

④、打包 ES6

打包处理 ES6 使用 babel-loaderbabel-corebabel-preset-envbabel-preset-es2015

npm 官网搜索该 loader,点击名称金进去查看安装手册,在终端执行:

$ cnpm i babel-loader babel-core babel-preset-env babel-preset-es2015 --save-dev
$ cnpm i babel-loader babel-core babel-preset-env babel-preset-es2015 --save-dev

附:如果运行出错可以安装:@babel/core@babel/preset-env;其中babel-loader 转换 js 加载器;@babel/corebabel 的核心模块;@babel/preser-envES6 转为 ES5babel-preset-es2015将部分 ES6 转化成 ES5 语法。

安装完该插件后,在根目录 package.json 中的 devDependencies 里能看到该 loader 和该 loader 的版本。

webpack.config.js 中的 module 选项中配置:

module.exports = {
  // ...
    module: {
      rules: [
       // ...
        {
            test: /\.m?js$/,
            exclude: /(node_modules|bower_components)/,
            use: {
              loader: 'babel-loader',
              options: {
                  presets: ['@babel/preset-env']
              }
            }
        }
      ]
  },
  // ...
}
module.exports = {
  // ...
    module: {
      rules: [
       // ...
        {
            test: /\.m?js$/,
            exclude: /(node_modules|bower_components)/,
            use: {
              loader: 'babel-loader',
              options: {
                  presets: ['@babel/preset-env']
              }
            }
        }
      ]
  },
  // ...
}

项目中处理页面数据交互一般都是存放单独脚本文件,因此在根目录下中的 src 目录下创建一个脚本文件 es6.js

let today = '今天天气很好';
alert(today);
let today = '今天天气很好';
alert(today);

在根目录 src/index.js 中引入该脚本文件:

document.write('hello webpack') 
require('./style.css')
require('./es6.js')
document.write('hello webpack') 
require('./style.css')
require('./es6.js')

此时运行 cnpm install 肯定会报错,因为需要设置 babel。在根目录下创建 .babelre 大致内容大致如下:

{
  'presets':['env']
}
{
  'presets':['env']
}

这时在终端执行 cnpm start 打包完成后,在浏览器上运行 index.html,能看到页面弹出的语句。

目前为止打包生成的 index.html 模版每次都得重新刷新页面。为了提高开发效率 Webpack 提供了开启服务热更新替换,不用刷新界面就能实现热更新。下面实现自动开启服务热更新。

devServer

查看 Webpack 手册安装相关模块,在终端执行:

$ cnpm i --save-dev webpack-dev-server
$ cnpm i --save-dev webpack-dev-server

安装成功后,在根目录 package.json 中的 devDependencies 里能看到 dev-serverdev-server 的版本

webpack.config.js 中配置:

// ...
  const webpack = require('webpack')

  module.exports = {
   // ...
    devServer: {
      contentBase:path.resolve(__dirname, 'dist'),
      host: 'localhost',
      port:8090,
      open: true,  // 自动打开浏览器
      hot: true  // 热更新
    }
 }
// ...
  const webpack = require('webpack')

  module.exports = {
   // ...
    devServer: {
      contentBase:path.resolve(__dirname, 'dist'),
      host: 'localhost',
      port:8090,
      open: true,  // 自动打开浏览器
      hot: true  // 热更新
    }
 }

修改 package.json 文件中的 scripts 字段里面的 start 属性对应的属性值:

// ...
  "scripts": {
    "start": "webpack-dev-server",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  // ...
// ...
  "scripts": {
    "start": "webpack-dev-server",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  // ...

也可以添加新的自定义启动 Webpack 命令,如 run 命令:

// ...
  "scripts": {
    "start": "webpack",
    "run": "webpack-dev-server",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  // ...
// ...
  "scripts": {
    "start": "webpack",
    "run": "webpack-dev-server",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  // ...

在终端执行 cnpm start 或者 cnpm run 会看到浏览器自动打开生成的 index.html。然后在 styl.css 中添加样式,如:font-size:30px 页面会自动更新并显示最新内容。

项目中或许会用到 jQuery 库或者 Vue,那么如何使用呢?继续往下看。

快捷导入

首先在 npm 官网查询要安装的包,如 jQuery,在终端执行:

$ cnpm install --save-dev jquery
$ cnpm install --save-dev jquery

安装完毕之后,在根目录 package.json 中的 devDependencies 里能看到 jQueryjQuery 的版本。

webpack.config.js 中的 plugins 选项中配置:

modules.exports = {
  // ...
  plugins: [
    // ...
      new webpack.ProvidePlugin({
      $: 'jquery',
    })
  ]
  // ...
}
modules.exports = {
  // ...
  plugins: [
    // ...
      new webpack.ProvidePlugin({
      $: 'jquery',
    })
  ]
  // ...
}

为了方便看效果,在根目录下的 src 中创建新的文件,如:es5.js

// es5.js
$('body').text('Hello World!!!')
// es5.js
$('body').text('Hello World!!!')

在根目录 src/index.js 中引入该脚本:

document.write('hello webpack') 
require('./style.css')
require('./es6.js')
require('./es5.js')
document.write('hello webpack') 
require('./style.css')
require('./es6.js')
require('./es5.js')

这时在页面上能看到 Hello World!!! 字样。到此为止一个入门级带有热更新的 Webpack 学习完毕。

总结

到这里入门级 Webpack 就算结束了。以上内容从认识 Webpack 到使用各种 loader 和各种 plugins 打包生成资源应用在项目中,认识了基本的使用语法和应用各种配置,也为以后进阶的学习夯实基础。

针对不同的项目,还有更多的 Webpack 打包细节需要调优,如:如何减少搜索文件;如何提高 loader 的打包速度;如何排除项目中无用的打包文件等等。在接下来的时间里,慢慢研究与学习。