首页 webpack学习笔记
文章
取消

webpack学习笔记

虽然在商家单页面案例中使用了webpack,但其实不是很理解webpack的工作原理。因此还是很有必要去学习一下webpack。

1. 安装webpack

在网上搜了一些教程,大都是全局安装并使用webpack。由于实际项目中大都是局部安装的。因此特意去搜了一下如何局部安装webpack:

  • 1) 新建一个文件夹exercise webpack,用来存放学习代码;
  • 2) 在此文件夹中打开CMD,运行:
    1
    
    npm init
    

    生成一个package.json文件;

  • 3) 安装webpack:
    1
    
    npm install webpack --save-dev
    
  • 4) 安装webpack-cli:
    1
    
    npm install webpack-cli --save-dev
    

    官网tips:如果你使用 webpack 4+ 版本,你还需要安装 CLI。
    完成后,打开package.json文件,我们会发现文件中多了devDependencies字段:

    1
    2
    3
    4
    5
    
    // devDependencies:开发和测试环境中依赖的包。项目上线之后不需要。
    "devDependencies": {
      "webpack": "^4.16.5",
      "webpack-cli": "^3.1.0"
    }
    

2. 使用webpack打包

2.1 打包多个js文件

  • 1) 在exercise webpack文件夹下新建名为‘01’的文件夹,并在其中新建index.html、app.js、01.js、02.js:
    index.html:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>index</title>
        <!-- <script src="../dist/main.js"></script> -->
    </head>
    <body>
      
    </body>
    </html>
    

    js:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    /* app.js文件 */
    const show = require('./01');// 引入01.js
    let str = show("Hello webpack!");
    document.write('<div>' + str + '</div>');
    /* 01.js文件 */
    const upper = require('./02');// 引入02.js
    module.exports = function show(str) {
      return upper(str);
    };
    /* 02.js文件 */
    module.exports = function upper(str) {
      return str.toUpperCase();
    };
    

    我们可以看出,app.js引入了01.js,而01.js引入了02.js。

  • 2) 在package.json文件中定义脚本命令"test01"
    1
    2
    3
    
    "scripts": {
      "test01": "webpack --mode=development 01/app.js"
    },
    

    --mode=development表示webpack的运行模式为开发模式。

  • 3) 在exercise webpack文件夹下打开CMD,运行:
    1
    
    npm run test01
    

    打包完成后,exercise webpack文件夹下会多一个dist文件夹,其中有个main.js文件;

  • 4) 在01/index.html文件中引入此main.js文件:
    1
    
    <script src="../dist/main.js"></script>
    

    用浏览器打开01/index.html文件,页面中出现:HELLO WEBPACK!。这说明webpack把app.js、01.js和02.js合并成了一个文件:main.js。

2.2 打包样式

在exercise webpack文件夹下拷贝一份01文件夹,命名为:02。

  • 1) 安装css-loader和style-loader。在exercise webpack文件夹下打开CMD,运行:
    1
    
    npm install css-loader style-loader --save-dev
    

    《入门webpack》中关于这两个加载器的解释:

    css-loader使你能够使用类似@import和url(…)的方法实现require的功能,style-loader将所有的计算后的样式加入页面中,二者组合在一起使你能够把样式表嵌入webpack打包后的js文件中。

  • 2) 在文件夹’02’中新建style.css、01.css和02.css:
    style.css文件:
    1
    2
    3
    
    /* 引入01.css和02.css文件 */
    @import './01.css';
    @import './02.css';
    

    01.css和02.css文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    /* 01.css文件 */
    div{
      background: tomato;
    }
    /* 02.css文件 */
    div{
      color: white;
      font-size: 40px;
    }
    
  • 3) 在02/app.js中引入style.css文件:
    1
    
    require('!style-loader!css-loader!./style.css');// 注意顺序:style-loader在前面
    

    注意:loader是从右向左执行的,即css-loader先执行,然后把结果传给style-loader处理,把样式打包到js文件中。因此要注意它们之间的顺序。

  • 4) 在package.json文件中定义脚本命令"test02"
    1
    2
    3
    4
    
    "scripts": {
      "test01": "webpack --mode=development 01/app.js",
      "test02": "webpack --mode=development 02/app.js"
    }
    
  • 5) 在exercise webpack文件夹下打开CMD,运行:
    1
    
    npm run test02
    

    运行完成后,dist/main.js会被新生成的main.js覆盖掉。

打开02/index.html,查看效果:

HELLO WEBPACK!

疑点: 其中有一次运行npm run test02时报错:

1
Error: Cannot find module 'p-limit'

安装p-limit模块后,接着报错:

1
Error: Cannot find module 'webpack-sources'

安装webpack-sources模块后,又报错:

1
Error: Cannot find module 'loader-runner'

在意识到可能不是模块丢失的原因后,我删除了node_modules文件夹,修改pakage.json中的"devDependencies"

1
2
3
4
5
6
  "devDependencies": {
    "css-loader": "^1.0.0",
    "style-loader": "^0.22.1",
    "webpack": "^4.16.5",
    "webpack-cli": "^3.1.0"
  }

然后运行:

1
npm install

完成之后,webpack运行正常。

3. webpack配置

webpack.docschina.org中对webpack配置的作用的描述:

因为 webpack 配置是标准的 Node.js CommonJS 模块,你可以做到以下事情:

  • 通过 require(…) 导入其他文件
  • 通过 require(…) 使用 npm 的工具函数
  • 使用 JavaScript 控制流表达式,例如 ?: 操作符
  • 对常用值使用常量或变量
  • 编写并执行函数来生成部分配置

3.1 基本配置文件webpack.config.js

webpack.docschina.org中对webpack.config.js文件的描述:

webpack 开箱即用,可以无需使用任何配置文件。然而,webpack 会假定项目的入口起点为 src/index,然后会在 dist/main.js 输出结果,并且在生产环境开启压缩和优化。
通常,你的项目还需要继续扩展此能力,为此你可以在项目根目录下创建一个 webpack.config.js 文件,webpack 会自动使用它

  • 1) 在配置webpack.config.js之前,我们先把文件夹’02’中的文件拷贝出来,重新整理一下:
    • index.html放在根目录下,即文件夹’exercise webpack’中;
    • 在根目录下新建文件夹’src’,并在src中新建文件夹’js’和’css’;
    • 把app.js、01.js和02.js放至src/js文件夹中;
    • 把style.css、01.css和02.css放至src/css文件夹中。
  • 2) 由于各文件之间的相对位置发生了变化,需要修改文件中的引用路径。
    index.html:
    1
    
    <script src="dist/main.js"></script>
    

    app.js:

    1
    2
    3
    
    // 修改前:require('!style-loader!css-loader!./style.css');
    require('../css/style.css');
    // 不需要在路径中添加'!style-loader!css-loader!',我们将在下一步的配置文件中为.css文件指定loader
    
  • 3) 在根目录下(即文件夹’exercise webpack’),新建webpack.config.js文件:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    
    const path = require('path');
    module.exports = {
      // 入口文件
      entry: './src/js/app.js',
      // 指定输出文件
      // 删除output.path: __dirname + '/dist',
      // 见代码后面的注释
      output: {
          filename: 'main.js'
      },
      // 模式
      mode: 'development',
      // 模块
      module: {
          // module.rules允许你在webpack配置中指定多个loader。
          rules:[
              {
                  // 正则表达式,以.css为扩展名的文件
                  test: /\.css$/,
                  // 指定使用的loader
                  use: [
                      {loader: 'style-loader'},
                      {loader: 'css-loader'}
                  ]
              }
          ]
      }
    };
    

    注:关于output选项,官网webpack.js.org中这样解释: output中至少要有一个值:filename,用于指定输出的文件名。
    webpack.config.js

    1
    2
    3
    4
    5
    
     module.exports = {
     output: {
       filename: 'bundle.js',
     }
     };
    

    不需要在配置中指定,webpack会自动在dist目录下生成文件bundle.js

    完成后的exercise webpack目录结构如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    exercise webpack
    ├── node_modules
    ├── src
    |   ├── css
    |   |   ├── 01.css
    |   |   ├── 02.css
    |   |   └── style.css
    |   └── js
    |       ├── 01.js
    |       ├── 02.js
    |       └── app.js
    ├── index.html
    ├── package.json
    └── webpack.config.js
    
  • 4) 在package.json文件中定义脚本命令"dev"
    1
    2
    3
    4
    5
    
    "scripts": {
      "test01": "webpack --mode=development 01/app.js",
      "test02": "webpack --mode=development 02/app.js",
      "dev": "webpack"
    },
    
  • 5) 在exercise webpack文件夹下打开CMD,运行:
    1
    
    npm run dev
    

    运行完成后,dist/main.js会被新生成的main.js覆盖掉。

打开根目录下的index.html,查看效果:(为了与前面的效果区分开,修改了src中相应的代码)

HELLO WEBPACK!——使用配置文件

4. webpack安装第三方库

如何在webpack工具里安装第三方库:(以jQuery为例)

  • 1) 在根目录处打开CMD,安装jQuery:
    1
    
    npm install jquery --save-dev
    
  • 2) 在app.js中引入jQuery,并使用:
    1
    2
    3
    4
    
    const $ = require('jquery');// 引入第三方库jQuery
    
    let div = $("div");
    div.html( div.html() + "——使用第三方库jQuery").css({'font-size': '20px'});
    
  • 3) 在exercise webpack文件夹下打开CMD,运行:
    1
    
    npm run dev
    

打开根目录下的index.html,查看效果:

HELLO WEBPACK!——使用配置文件——使用第三方库jQuery

5. webpack-dev-server模块

修改代码后,手动运行npm run dev会很繁琐。我们可以使用webpack-dev-server,让代码发生变化后自动编译。
关于webpack-dev-server模块的功能,以下引用了github页面的原文描述:

Use webpack with a development server that provides live reloading. This should be used for development only.

让webpack提供具有实时重载功能的开发服务器。 这应该仅用于开发模式(mode: ‘development’)。

5.1 安装和使用

  • 1) 安装此模块
    1
    
    npm install webpack-dev-server --save-dev
    
  • 2) 在package.json文件中定义脚本命令"start"
    1
    2
    3
    4
    5
    6
    
    "scripts": {
      "test01": "webpack --mode=development 01/app.js",
      "test02": "webpack --mode=development 02/app.js",
      "dev": "webpack",
      "start": "webpack-dev-server --output-filename ./dist/main.js"
    }
    
  • 3) 在exercise webpack文件夹下打开CMD,运行:
    1
    
    npm run start
    

    我们会看到提示:Project is running at http://localhost:8080/

在浏览器中打开此地址,我们会看到和3中同样的内容:

HELLO WEBPACK!——使用配置文件——使用第三方库jQuery

现在,我们修改js或者css文件后,webpack-dev-server模块会自动打包文件,因此我们在浏览器就能查看最新的页面。
注意:

  1. "start"命令中,webpack-dev-server后面添加参数--output-filename ./dist/main.js的原因:
    webpack会自动使用webpack.config.js中的output.filename: "main.js",然后在本地文件夹dist下生成打包文件main.js
    但是webpack-dev-server不会,它生成的文件在内存中,并且我们可以通过http://localhost:8080/访问。
    所以添加参数--output-filename ./dist/main.js之后,打包文件的路径将是:http://localhost:8080/dist/main.js。 否则将会是:http://localhost:8080/main.js

  2. index.html中,导入main.jsscript标签如下:
    1
    
       <script src="dist/main.js"></script>
    

    我们访问index.html时,会向下面的URL发送GET请求,获取上述打包文件main.js

    1
    2
    
       Request URL: http://localhost:8080/dist/main.js
       Request Method: GET
    

5.2 devServer

devServer选项影响webpack-dev-server的行为。
在前面的例子中,我们在package.json文件中定义了脚本命令"start",并把参数写在了命令webpack-dev-server后面:

1
2
3
"scripts": {
  "start": "webpack-dev-server --output-filename ./dist/main.js"
}

但是在实际项目中webpack-dev-server会需要更多的配置参数,因此我们修改此命令,使用配置文件:

1
2
3
"scripts": {
  "start": "webpack-dev-server --config webpack.config.js"
}

然后最重要的一点是,我们要在webpack.config.js文件中新增devServer.publicPath属性:

1
2
3
4
devServer: {
  // 打包的文件能通过http://localhost:8080/dist/{output.filename}访问
  publicPath: '/dist/'
}

下面是devServer中常用的几个属性:

  • 1) devServer.publicPath
    指定能够访问打包文件的路径名,在下例中的配置可以让我们通过http://localhost:8080/dist/main.js访问打包后的js文件:
    1
    2
    3
    4
    
    devServer: {
      // 打包的文件能通过http://localhost:8080/dist/{output.filename}访问
      publicPath: '/dist/'
    }
    
  • 2) devServer.port
    指定要监听请求的端口号。
    1
    2
    3
    
    devServer: {
      port: 8080
    }
    
  • 3) devServer.before
    用于指定在其他中间件前执行的自定义中间件,引用官网的描述:

    Provides the ability to execute custom middleware prior to all other middleware internally within the server. This could be used to define custom handlers, for example:

    1
    2
    3
    4
    5
    6
    7
    
    devServer: {
     before(app){
       app.get('/some/path', function(req, res) {
         res.json({ custom: 'response' });
       });
     }
    }
    

6. 插件(plugins)

以下内容来自于webpack.docschina.org
插件是 webpack 的支柱功能。它的目的在于解决 loader 无法实现的其他事。
webpack 插件是一个具有 apply 方法的 JavaScript 对象。apply 属性会被 webpack compiler 调用,并且 compiler 对象可在整个编译生命周期访问。

6.1 插件的使用和配置

以下内容来自于webpack.docschina.org
由于插件可以携带参数/选项,你必须在 webpack 配置中,向 plugins 属性传入 new 实例。
根据你的 webpack 用法,这里有多种方式使用插件。

  • 1) 配置
    webpack.config.js
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    const HtmlWebpackPlugin = require('html-webpack-plugin'); //通过 npm 安装
    const webpack = require('webpack'); //访问内置的插件
    const path = require('path');
    
    module.exports = {
      entry: './path/to/my/entry/file.js',
      output: {
        filename: 'my-first-webpack.bundle.js',
        path: path.resolve(__dirname, 'dist')
      },
      module: {
        rules: [
          {
            test: /\.(js|jsx)$/,
            use: 'babel-loader'
          }
        ]
      },
      plugins: [
        new HtmlWebpackPlugin({template: './src/index.html'})
      ]
    };
    
  • 2) Node API
    some-node-script.js
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    const webpack = require('webpack'); //访问 webpack 运行时(runtime)
    const configuration = require('./webpack.config.js');
    
    let compiler = webpack(configuration);
    compiler.apply(new webpack.ProgressPlugin());
    
    compiler.run(function(err, stats) {
      // ...
    });
    

6.2 插件html-webpack-plugin

在前面的例子中,我们使用了webpack-dev-server模块,并在index.html中通过script标签手动引入打包后的js文件。

1
<script src="dist/main.js"></script>

我们可以使用html-webpack-plugin插件,自动完成上述工作,并以指定的html文件为模板生成一个html文件。 在这之前,我们先注销根目录下index.html中引入main.js文件的script标签。

  • 1) 安装html-webpack-plugin
    1
    
    npm install --save-dev html-webpack-plugin
    
  • 2) 修改webpack.config.js
    新增插件:
    1
    2
    3
    4
    5
    6
    
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    // 插件
    plugins: [
    // 以根目录下的index.html为模板
    new HtmlWebpackPlugin({template: 'index.html', filename: 'index.html'})
    ]
    

    删除devServer.publicPath的配置:

    1
    2
    3
    4
    
    devServer: {
      // 打包的文件能通过http://localhost:8080/dist/{output.filename}访问
      // publicPath: '/dist/'
    }
    

    注意:如果不删除此配置,html-webpack-plugin生成的index.html的路径将会为:http://localhost:8080/dist/index.html
    而我们的目的是想在根目录下生成index.html,这样我们访问http://localhost:8080/时,会自动打开此index.html

最后我们运行npm run start,并在浏览器中打开http://localhost:8080/,就能看到内容了。
用浏览器查看此网页的代码如下:

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>index</title>
    <!--<script src="dist/main.js"></script>-->
</head>
<body>
<script type="text/javascript" src="main.js"></script></body>
</html>

我们可以看到body标签中多了一个引入main.js文件的script标签。

7. webpack中使用babel

官网www.babeljs.cn中是这样描述babel的:

Babel是一个JavaScript编译器。它通过语法转换器支持最新版本的JavaScript。这些插件允许你立刻使用新语法,无需等待浏览器支持。

7.1 为何要使用babel

在本篇的例子中,除了let和const的使用,没有使用ES6的其他语法。因此,我们用IE11浏览器能正常访问http://localhost:8080/
注:IE11支持一小部分ES6,如let和const的;IE10几乎不支持ES6。
如果我们在js文件中使用更多的ES6语法,我们将无法用IE11浏览器访问http://localhost:8080/
app.js文件:

1
2
3
4
5
6
7
8
import show from './01';// 引入01.js(ES6)
import '../css/style.css';// 引入style.css样式文件(ES6)
import $ from 'jquery';// 引入第三方库jQuery(ES6)

let str = show("Hello webpack!——使用配置文件");
document.write('<div>' + str + '</div>');
let div = $("div");
div.html( div.html() + "——使用第三方库jQuery").css({'font-size': '20px'});

01.js文件:

1
2
3
4
5
import upper from './02';// 引入02.js(ES6)

export default function (str) {
    return upper(str);
}

02.js文件:

1
2
3
4
5
// 箭头函数(ES6)
let show = (str) => {
    return str.toUpperCase();
};
export default show;

但是使用已经支持ES6的浏览器,如最新版本的chrome、firefox等等,是能够正常访问的。为了兼容未支持ES6的浏览器,我们可以使用babel把ES6语法转换成ES5。

7.2 安装和使用babel

关于如何在webpack中使用babel,参考了官网www.babeljs.cn中的步骤。

  • 1) 安装
    1
    
    npm install babel-loader babel-core --save-dev
    
  • 2) 修改webpack.config.js文件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    module: {
      rules: [
        {
          test: /\.js$/,
          exclude: /node_modules/,// 排除特定条件
          use: ['babel-loader']// [{loader: 'label-loader'}]的简写
        }       
      ]
    }
    
  • 3) 安装babel-preset-env,根据目标浏览器或运行时环境,自动决定适合的Babel插件和polyfills,从而将ES2015+编译为ES5。
    1
    
    npm install babel-preset-env --save-dev
    
  • 4) 创建.babelrc文件:
    1
    2
    3
    
    {
    "presets": ["env"]
    }
    

完成babel的安装和配置后,运行npm run start,之后就可以用IE11浏览器正常访问http://localhost:8080了。但IE8无法正常访问,因为IE8不支持ES5。

8. webpack模块

在模块化编程中,开发者将程序分解成离散功能块(discrete chunks of functionality),并称之为模块。

8.1 什么是 webpack 模块

此节引用自webpack.docschina.org

对比 Node.js 模块,webpack 模块能够以各种方式表达它们的依赖关系,几个例子如下:

  • ES2015 import 语句
  • CommonJS require() 语句
  • AMD define 和 require 语句
  • css/sass/less 文件中的 @import 语句。
  • 样式(url(…))或 HTML 文件(<img src=…>)中的图片链接(image url)

tips:webpack 1 需要特定的 loader 来转换 ES 2015 import,然而通过 webpack 2 可以开箱即用。

8.2 模块解析

此节引用自webpack.docschina.org

resolver 是一个库(library),用于帮助找到模块的绝对路径。一个模块可以作为另一个模块的依赖模块,然后被后者引用,如下:

1
2
3
import foo from 'path/to/module'
// 或者
require('path/to/module')

所依赖的模块可以是来自应用程序代码或第三方的库(library)。resolver 帮助 webpack 找到 bundle 中需要引入的模块代码,这些代码在包含在每个 require/import 语句中。 当打包模块时,webpack 使用 enhanced-resolve 来解析文件路径。
使用 enhanced-resolve,webpack 能够解析三种文件路径:

  • 绝对路径
    1
    2
    
    import "/home/me/file";// '/'表示根目录
    import "C:\\Users\\me\\file";
    
  • 相对路径
    1
    2
    
    import "../src/file1";// '../'表示当前文件夹的上一级文件夹
    import "./file2";// './'表示当前文件夹
    
  • 模块路径
    1
    2
    
    import "module";
    import "module/lib/file";
    

    模块将在 resolve.modules 中指定的所有目录内搜索。 你可以替换初始模块路径,此替换路径通过使用 resolve.alias 配置选项来创建一个别名。

本文由作者按照 CC BY 4.0 进行授权