美文网首页webpack
webpack 笔记 指南篇(一)

webpack 笔记 指南篇(一)

作者: 秀秀_2d78 | 来源:发表于2019-12-10 20:53 被阅读0次

    webpack 用于编译 JavaScript 模块。一旦完成安装,你就可以通过 webpack 的 CLI 或 API 与其配合交互。

    一、安装

    要安装最新版本或特定版本,请运行以下命令之一:

    $ npm install --save-dev webpack
    $ npm install --save-dev webpack@<version>
    

    如果你使用 webpack 4+ 版本,你还需要安装 CLI。

    $ npm install --save-dev webpack-cli
    

    全局安装
    以下的 NPM 安装方式,将使 webpack 在全局环境下可用:

    $ npm install --global webpack
    

    不推荐全局安装 webpack。这会将你项目中的 webpack 锁定到指定版本,并且在使用不同的 webpack 版本的项目中,可能会导致构建失败。

    二、起步

    1)创建一个本地页面
    1、首先我们创建一个目录,初始化 npm,然后 在本地安装 webpack,接着安装 webpack-cli(此工具用于在命令行中运行 webpack):

    $ mkdir webpack-demo && cd webpack-demo      //创建目录
    $ npm init -y                                //创建package.json文件
    $ npm install webpack webpack-cli --save-dev //安装 webpack-cli
    

    2、现在我们将创建以下目录结构、文件和内容:

      webpack-demo
      |- package.json
    + |- /dist
    +   |- index.html
    + |- /src
    +   |- index.js
    
    • “源”代码(/src)是用于书写和编辑的代码。
    • “分发”代码(/dist)是构建后代码最小化和优化后的“输出”目录
    • 要在 index.js 中打包 lodash 依赖,我们需要在本地安装 library:
    $ npm install --save lodash
    
    • src/index.js
    import _ from 'lodash';
    function component() {
      var element = document.createElement('div');
      // lodash 是由当前 script 脚本 import 导入进来的
     element.innerHTML = _.join(['Hello', 'webpack'], ' ');
      return element;
    }
    document.body.appendChild(component());
    
    • index.html
    <!doctype html>
    <html>
      <head>
        <title>起步</title>
      </head>
      <body>
     <script src="main.js"></script>
      </body>
    </html>
    

    3、执行

    $ npx webpack
    

    4、在浏览器中打开 index.html,如果一切访问都正常,你应该能看到以下文本:'Hello webpack'。

    2)模块

    • ES2015 中的 import 和 export 语句已经被标准化。虽然大多数浏览器还无法支持它们,但是 webpack 却能够提供开箱即用般的支持。
    • webpack 不会更改代码中除 import 和 export 语句以外的部分。如果你在使用其它 ES2015 特性,请确保你在 webpack 的 loader 系统中使用了一个像是 Babel 的转译器。

    3)使用一个配置文件
    webpack.config.js

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

    4)NPM 脚本(NPM Scripts)
    在 package.json 添加一个 npm 脚本(npm script):

    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "dev": "webpack --mode development",
        "build": "webpack --mode production"
    }
    

    现在,可以使用 npm run dev 命令,来替代我们之前使用的 npx 命令。
    5)结论
    现在,你已经实现了一个基本的构建过程,此刻你的项目应该和如下类似:

    webpack-demo
    |- package.json
    |- webpack.config.js
    |- /dist
      |- bundle.js
      |- index.html
    |- /src
      |- index.js
    |- /node_modules
    

    如果你使用的是 npm 5,你可能还会在目录中看到一个 package-lock.json 文件。

    三、管理资源

    webpack 最出色的功能之一就是,除了 JavaScript,还可以通过 loader 引入任何其他类型的文件。
    1)加载 CSS
    为了从 JavaScript 模块中 import 一个 CSS 文件,你需要在 module 配置中 安装并添加 style-loader 和 css-loader:

    $ npm install --save-dev style-loader css-loader
    

    webpack.config.js中添加模块

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

    webpack 根据正则表达式,来确定应该查找哪些文件,并将其提供给指定的 loader。在这种情况下,以 .css 结尾的全部文件,都将被提供给 style-loader 和 css-loader。

    我们尝试一下,通过在项目中添加一个新的 style.css 文件,并将其导入到我们的 index.js 中:

      |- /src
        |- style.css
        |- index.js
    

    src/style.css

    .hello {
      color: red;
    }
    

    src/index.js

    import _ from 'lodash';
    import './style.css';                  //引入css
    function component() {
      var element = document.createElement('div');
      // lodash 是由当前 script 脚本 import 导入进来的
     element.innerHTML = _.join(['Hello', 'webpack'], ' ');
     element.classList.add('hello');        //添加节点
      return element;
    }
    document.body.appendChild(component());
    

    现在运行构建命令:

    $ npm run build
    

    浏览器中打开 index.html,你应该看到 Hello webpack 现在的样式是红色。
    查看页面的 head 标签。它包含了我们在 index.js 中导入的 style 块元素。

    2)加载图片
    背景和图标这些图片,使用 file-loader,我们可以轻松地将这些内容混合到 CSS 中:

    $ npm install --save-dev file-loader
    

    webpack.config.js中添加

     module: {
           {
             test: /\.(png|svg|jpg|gif)$/,
             use: [ 'file-loader']
          }
    }
    

    我们向项目添加一个图像,然后看它是如何工作的,你可以使用任何你喜欢的图像:

      |- /src
        |- icon.png
        |- style.css
        |- index.js
    

    src/index.js

    import _ from 'lodash';
    import './style.css';              
    import Icon from './icon.png';           //引入图片
    function component() {
      var element = document.createElement('div');
      // lodash 是由当前 script 脚本 import 导入进来的
      element.innerHTML = _.join(['Hello', 'webpack'], ' ');
      element.classList.add('hello');   
            
      var myIcon = new Image();              //将图像添加到我们现有的 div
      myIcon.src = Icon;
      element.appendChild(myIcon);
      return element;
    }
    document.body.appendChild(component());
    

    src/style.css

      .hello {
        color: red;
        background: url('./icon.png');
      }
    

    现在运行构建命令:

    $ npm run build
    

    如果一切顺利,在Hello webpack 文本旁边将显示 img 元素。

    想要压缩和优化你的图像。查看 image-webpack-loader 和 url-loader,以了解更多关于如果增强加载处理图片功能。

    3)加载字体
    像字体这样的其他资源如何处理呢?file-loader 和 url-loader 可以接收并加载任何文件,这就是说,我们可以将它们用于任何类型的文件,包括字体。让我们更新 webpack.config.js 来处理字体文件:
    webpack.config.js中添加:

    module: {
         {
             test: /\.(woff|woff2|eot|ttf|otf)$/,
             use: [ 'file-loader' ]
           }
     }
    

    在项目中添加一些字体文件:

      |- /src
        |- my-font.woff
        |- my-font.woff2
        |- icon.png
        |- style.css
        |- index.js
    

    通过一个 @font-face 声明引入。本地的 url(...) 指令会被 webpack 获取处理,就像它处理图片资源一样:
    src/style.css

     @font-face {
       font-family: 'MyFont';
       src:  url('./my-font.woff2') format('woff2'),
             url('./my-font.woff') format('woff');
       font-weight: 600;
      font-style: normal;
     }
      .hello {
        color: red;
        font-family: 'MyFont';
        background: url('./icon.png');
      }
    

    现在运行构建命令:

    $ npm run build
    

    重新打开 index.html 看看我们的 Hello webpack 文本显示是否换上了新的字体。如果一切顺利,你应该能看到变化。

    4)加载数据
    此外,可以加载的有用资源还有数据JSON 支持实际上是内置的,也就是说 import Data from './data.json' 默认将正常运行。

    四、管理输出

    随着引入所有资源的增长,手动地对 index.html 文件进行管理,一切就会变得困难起来。然而,可以通过一些插件,会使这个过程更容易操控。
    1)预先准备
    首先,让我们调整一下我们的项目:

      webpack-demo
      |- package.json
      |- webpack.config.js
      |- /dist
      |- /src
        |- index.js
        |- print.js
      |- /node_modules
    

    我们在 src/print.js 文件中添加一些逻辑:
    src/print.js

    export default function printMe() {
      console.log('I get called from print.js!');
    }
    

    并且在 src/index.js 文件中使用这个函数:
    src/index.js

      import _ from 'lodash';
      import printMe from './print.js';
      function component() {
        var element = document.createElement('div');
        var 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());
    

    我们还要更新 dist/index.html 文件,来为 webpack 分离入口做好准备:
    dist/index.html

      <!doctype html>
      <html>
        <head>
         <title>Output Management</title>
         <script src="./print.bundle.js"></script>
        </head>
        <body>
         <script src="./app.bundle.js"></script>
        </body>
      </html>
    

    现在调整配置。我们将在 entry 添加 src/print.js 作为新的入口起点(print),然后修改 output,以便根据入口起点名称动态生成 bundle 名称:
    webpack.config.js

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

    现在运行构建命令:

    $ npm run build
    

    我们可以看到,webpack 生成 print.bundle.js 和 app.bundle.js 文件,这也和我们在 index.html 文件中指定的文件名称相对应。如果你在浏览器中打开 index.html,就可以看到在点击按钮时会发生什么。

    2)清理 /dist 文件夹
    在每次构建前清理 /dist 文件夹,是比较推荐的做法,因此只会生成用到的文件。clean-webpack-plugin 是一个比较普及的管理插件,让我们安装和配置下。

    $ npm install clean-webpack-plugin --save-dev
    

    webpack.config.js

      const path = require('path');
      const HtmlWebpackPlugin = require('html-webpack-plugin');
     const { CleanWebpackPlugin } = require("clean-webpack-plugin");  //新增
    
      module.exports = {
        entry: {
          app: './src/index.js',
          print: './src/print.js'
        },
        plugins: [
         new CleanWebpackPlugin(),        //新增
          new HtmlWebpackPlugin({
            title: 'Output Management'
          })
        ],
        output: {
          filename: '[name].bundle.js',
          path: path.resolve(__dirname, 'dist')
        }
      };
    

    现在执行 npm run build,再检查 /dist 文件夹。如果一切顺利,你现在应该不会再看到旧的文件,只有构建后生成的文件!

    五、开发

    1)使用 source map
    当 webpack 打包源代码时,可能会很难追踪到错误和警告在源代码中的原始位置。
    为了更容易地追踪错误和警告,JavaScript 提供了 [source map]功能,将编译后的代码映射回原始源代码。如果一个错误来自于 b.js,source map 就会明确的告诉你。
    我们使用 inline-source-map 选项,这有助于解释说明我们的目的。
    webpack.config.js

      module.exports = {
        entry: {
          app: './src/index.js',
          print: './src/print.js'
        },
       devtool: 'inline-source-map',      //使用 inline-source-map 选项
        plugins: [
          new CleanWebpackPlugin(['dist']),
          new HtmlWebpackPlugin({
            title: 'Development'
          })
        ],
    

    现在,让我们来做一些调试,在 print.js 文件中生成一个错误:
    src/print.js

      export default function printMe() {
    -   console.log('I get called from print.js!');
    +   cosnole.error('I get called from print.js!');
      }
    

    运行 npm run build
    现在在浏览器打开最终生成的 index.html 文件,点击按钮,并且在控制台查看显示的错误。错误应该如下:

     Uncaught ReferenceError: cosnole is not defined
        at HTMLButtonElement.printMe (print.js:2)
    

    我们可以看到,此错误包含有发生错误的文件(print.js)和行号(2)的引用。这是非常有帮助的,因为现在我们知道了,所要解决的问题的确切位置。

    2)选择一个开发工具
    每次要编译代码时,手动运行 npm run build 就会变得很麻烦。
    webpack 中有几个不同的选项,可以帮助你在代码发生变化后自动编译代码:

    • webpack's Watch Mode
    • webpack-dev-server
    • webpack-dev-middleware

    多数场景中,你可能需要使用 webpack-dev-server,但是不妨探讨一下以上的所有选项。

    3)观察模式
    我们添加一个用于启动 webpack 的观察模式的 npm script 脚本:

    package.json

        "scripts": {
          "test": "echo \"Error: no test specified\" && exit 1",
    +     "watch": "webpack --watch",
          "build": "webpack"
        },
    

    现在,你可以在命令行中运行 npm run watch,就会看到 webpack 编译代码,然而却不会退出命令行。这是因为 script 脚本还在观察文件。
    现在,修改保存文件并检查终端窗口。应该可以看到 webpack 自动重新编译修改后的模块!

    4)使用 webpack-dev-server
    webpack-dev-server 提供了一个简单的 web 服务器,并且能够实时重新加载(live reloading)。让我们设置以下:

    npm install --save-dev webpack-dev-server
    
    

    修改配置文件,告诉开发服务器(dev server),在哪里查找文件:
    webpack.config.js

        devtool: 'inline-source-map',
    +   devServer: {
    +     contentBase: './dist'
    +   },
    

    以上配置告知 webpack-dev-server,在 localhost:8080 下建立服务,将 dist 目录下的文件,作为可访问文件。
    让我们添加一个 script 脚本,可以直接运行开发服务器(dev server):
    package.json

       "scripts": {
          "test": "echo \"Error: no test specified\" && exit 1",
          "watch": "webpack --watch",
    +     "start": "webpack-dev-server --open",
          "build": "webpack"
        },
    

    现在,我们可以在命令行中运行 npm start,就会看到浏览器自动加载页面。如果现在修改和保存任意源文件,web 服务器就会自动重新加载编译后的代码

    5)使用 webpack-dev-middleware
    webpack-dev-middleware 是一个容器(wrapper),它可以把 webpack 处理后的文件传递给一个服务器(server)。
    同时,它也可以作为一个单独的包来使用,以便进行更多自定义设置来实现更多的需求。
    首先,安装 express 和 webpack-dev-middleware:

    npm install --save-dev express webpack-dev-middleware
    

    webpack.config.js

        output: {
          filename: '[name].bundle.js',
          path: path.resolve(__dirname, 'dist'),
    +     publicPath: '/'
        }
    

    下一步就是设置我们自定义的 express 服务:
    project

      webpack-demo
      |- package.json
      |- webpack.config.js
    + |- server.js
      |- /dist
      |- /src
        |- index.js
        |- print.js
      |- /node_modules
    

    server.js

    const express = require('express');
    const webpack = require('webpack');
    const webpackDevMiddleware = require('webpack-dev-middleware');
    
    const app = express();
    const config = require('./webpack.config.js');
    const compiler = webpack(config);
    
    //告诉express使用webpack-dev-middleware并使用webpack.config.js
    //以配置文件为基础。
    app.use(webpackDevMiddleware(compiler, {
      publicPath: config.output.publicPath
    }));
    
    //服务监听3000端口。
    app.listen(3000, function () {
      console.log('Example app listening on port 3000!\n');
    });
    

    现在,添加一个 npm script,以使我们更方便地运行服务:
    package.json

    "scripts": {
         "test": "echo \"Error: no test specified\" && exit 1",
         "watch": "webpack --watch",
         "start": "webpack-dev-server --open",
    +     "server": "node server.js",
         "build": "webpack"
       },
    

    现在,在你的终端执行 npm run server
    打开浏览器,跳转到 http://localhost:3000,你应该看到你的webpack 应用程序已经运行!

    六、模块热替换

    模块热替换功能会在应用程序运行过程中替换、添加或删除模块而无需重新加载整个页面。
    它允许在运行时更新各种模块,而无需进行完全刷新。

    1)启用 HMR
    启用此功能实际上相当简单。而我们要做的,就是更新 webpack-dev-server的配置,和使用 webpack 内置的 HMR 插件。我们还要删除掉 print.js 的入口起点,因为它现在正被 index.js 模块使用。
    webpack.config.js

    const path = require('path');
      const HtmlWebpackPlugin = require('html-webpack-plugin');
      const CleanWebpackPlugin = require('clean-webpack-plugin');
    + const webpack = require('webpack');
    
      module.exports = {
        entry: {
    -      app: './src/index.js',
    -      print: './src/print.js'
    +      app: './src/index.js'
        },
        devtool: 'inline-source-map',
        devServer: {
          contentBase: './dist',
    +     hot: true
        },
        plugins: [
          new CleanWebpackPlugin(['dist']),
          new HtmlWebpackPlugin({
            title: 'Hot Module Replacement'
          }),
    +     new webpack.NamedModulesPlugin(),
    +     new webpack.HotModuleReplacementPlugin()
        ],
        output: {
          filename: '[name].bundle.js',
          path: path.resolve(__dirname, 'dist')
        }
      };
    

    package.json

    "scripts": {
          "test": "echo \"Error: no test specified\" && exit 1",
          "watch": "webpack --watch",
    -      "start": "webpack-dev-server --open",
    +      "start": "webpack-dev-server --hotOnly",
           "build": "webpack"
        },
    

    在起步阶段,我们将通过在命令行中运行 npm start 来启动并运行 dev server。

    修改 index.js 文件,以便当 print.js 内部发生变更时可以告诉 webpack 接受更新的模块。
    index.js

      document.body.appendChild(component());
    +
    + if (module.hot) {
    +   module.hot.accept('./print.js', function() {
    +     console.log('Accepting the updated printMe module!');
    +     printMe();
    +   })
    + }
    
    

    更改 print.js 中 console.log 的输出内容。
    print.js

      export default function printMe() {
    -   console.log('I get called from print.js!');
    +   console.log('Updating print.js...')
      }
    

    你将会在浏览器中看到如下的输出。
    console

    [HMR] Waiting for update signal from WDS...
    main.js:4395 [WDS] Hot Module Replacement enabled.
    + 2main.js:4395 [WDS] App updated. Recompiling...
    + main.js:4395 [WDS] App hot update...
    + main.js:4330 [HMR] Checking for updates on the server...
    + main.js:10024 Accepting the updated printMe module!
    + 0.4b8ee77….hot-update.js:10 Updating print.js...
    + main.js:4330 [HMR] Updated modules:
    + main.js:4330 [HMR]  - 20
    + main.js:4330 [HMR] Consider using the NamedModulesPlugin for module names.
    

    2)通过 Node.js API
    当使用 webpack dev server 和 Node.js API 时,不要将 dev server 选项放在 webpack 配置对象(webpack config object)中。而是,在创建选项时,将其作为第二个参数传递。例如:
    new WebpackDevServer(compiler, options)
    想要启用 HMR,还需要修改 webpack 配置对象,使其包含 HMR 入口起点。webpack-dev-server package 中具有一个叫做 addDevServerEntrypoints 的方法,你可以通过使用这个方法来实现。这是关于如何使用的一个小例子:
    dev-server.js

    const webpackDevServer = require('webpack-dev-server');
    const webpack = require('webpack');
    
    const config = require('./webpack.config.js');
    const options = {
      contentBase: './dist',
      hot: true,
      host: 'localhost'
    };
    
    webpackDevServer.addDevServerEntrypoints(config, options);
    const compiler = webpack(config);
    const server = new webpackDevServer(compiler, options);
    
    server.listen(5000, 'localhost', () => {
      console.log('dev server listening on port 5000');
    });
    

    3)问题
    模块热替换可能比较难掌握。为了说明这一点,我们回到刚才的示例中。如果你继续点击示例页面上的按钮,你会发现控制台仍在打印这旧的 printMe 功能。

    这是因为按钮的 onclick 事件仍然绑定在旧的 printMe 函数上。

    为了让它与 HMR 正常工作,我们需要使用 module.hot.accept 更新绑定到新的 printMe 函数上:
    index.js

    - document.body.appendChild(component());
    + let element = component(); // 当 print.js 改变导致页面重新渲染时,重新获取渲染的元素
    + document.body.appendChild(element);
    
      if (module.hot) {
        module.hot.accept('./print.js', function() {
          console.log('Accepting the updated printMe module!');
    -     printMe();
    +     document.body.removeChild(element);
    +     element = component(); // 重新渲染页面后,component 更新 click 事件处理
    +     document.body.appendChild(element);
        })
      }
    

    4)HMR 修改样式表
    借助于 style-loader 的帮助,CSS 的模块热替换实际上是相当简单的。当更新 CSS 依赖模块时,此 loader 在后台使用 module.hot.accept 来修补(patch) <style> 标签。

    所以,可以使用以下命令安装两个 loader :

    npm install --save-dev style-loader css-loader
    
    

    接下来我们来更新 webpack 的配置,让这两个 loader 生效。

    webpack.config.js

    +   module: {
    +     rules: [
    +       {
    +         test: /\.css$/,
    +         use: ['style-loader', 'css-loader']
    +       }
    +     ]
    +   },
    

    热加载样式表,与将其导入模块一样简单:
    project

      webpack-demo
      | - package.json
      | - webpack.config.js
      | - /dist
        | - bundle.js
      | - /src
        | - index.js
        | - print.js
    +   | - styles.css
    

    styles.css

    body {
      background: blue;
    }
    

    index.js

      import _ from 'lodash';
      import printMe from './print.js';
    + import './styles.css';
    

    将 body 上的样式修改为 background: red;,你应该可以立即看到页面的背景颜色随之更改,而无需完全刷新。
    styles.css

      body {
    -   background: blue;
    +   background: red;
      }
    

    相关文章

      网友评论

        本文标题:webpack 笔记 指南篇(一)

        本文链接:https://www.haomeiwen.com/subject/fcuqictx.html