• webpack 入门教程


    webpack 是什么?

    本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。

     
    webpack

    快速了解几个基本的概念

    mode 开发模式

    webpack 提供 mode 配置选项,配置 webpack 相应模式的内置优化。

    // webpack.production.config.js
    module.exports = {
    +  mode: 'production',
    }
    

    入口文件(entry)

    入口文件,类似于其他语言的起始文件。比如:c 语言的 main 函数所在的文件。

    入口起点(entry point)指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。

    可以在 webpack 的配置文件中配置入口,配置节点为: entry,当然可以配置一个入口,也可以配置多个。

    输出(output)

    output 属性告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件。

    const path = require('path');
    
    module.exports = {
      entry: './path/to/my/entry/file.js',
      output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'my-first-webpack.bundle.js'
      }
    };
    

    loader

    loader 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后你就可以利用 webpack 的打包能力,对它们进行处理。

    插件(plugins)

    loader 被用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。插件接口功能极其强大,可以用来处理各种各样的任务。

    webpack 的安装

    请确保安装了 Node.js 的最新版本。而且已经在您的项目根目录下已经初始化好了最基本的package.json文件

    本地安装 webpack

    $ npm install --save-dev webpack
    
    # 如果你使用 webpack 4+ 版本,你还需要安装 CLI。
    npm install --save-dev webpack-cli
    

    安装完成后,可以添加npmscript脚本

    // package.json
    "scripts": {
        "start": "webpack --config webpack.config.js"
    }
    

    全局安装 webpack(不推荐)

    将使 webpack 在全局环境下可用:

    npm install --global webpack
    

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

    快速入门完整 demo

    • 第一步:创建项目结构

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

    mkdir webpack-demo && cd webpack-demo
    npm init -y
    npm install webpack webpack-cli --save-dev
    

    项目结构

      webpack-demo
    + |- package.json
    + |- /dist
    +   |- index.html
    + |- /src
    +   |- index.js
    
    • 第二步:安装 loadash 依赖和编写 js 文件
    npm install --save lodash
    

    编写:src/index.js 文件

    import _ from 'lodash';
    
    function createDomElement() {
      let dom = document.createElement('div');
      dom.innerHTML = _.join(['aicoder', '.com', ' wow'], '');
      return dom;
    }
    
    document.body.appendChild(createDomElement());
    

    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>起步</title>
    </head>
    <body>
      <script src="./main.js"></script>
    </body>
    </html>
    
    • 第三步:编写 webpack 配置文件

    根目录下添加 webpack.config.js文件。

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

    webpack.config.js 内容如下:

    const path = require('path');
    
    module.exports = {
      mode: 'development',
      entry: './src/index.js',
      output: {
        filename: 'main.js',
        path: path.resolve(__dirname, './dist')
      }
    };
    
    • 执行构建任务

    直接执行构建任务:

    npx webpack
    

    打开: dist/index.html 可以查看到页面的结果。

    加载非 js 文件

    webpack 最出色的功能之一就是,除了 JavaScript,还可以通过 loader 引入任何其他类型的文件

    加载 CSS 文件

    • 第一步: 安装 css 和 style 模块解析的依赖 style-loader 和 css-loader
    npm install --save-dev style-loader css-loader
    
    • 第二步: 添加 css 解析的 loader
    const path = require('path');
    
    module.exports = {
      entry: './src/index.js',
      output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'dist')
      },
      module: {
        rules: [
          {
            test: /.css$/,
            use: ['style-loader', 'css-loader']
          }
        ]
      }
    };
    
    • css-loader: 辅助解析 js 中的 import './main.css'
    • style-loader: 把 js 中引入的 css 内容 注入到 html 标签中,并添加 style 标签.依赖 css-loader

    你可以在依赖于此样式的 js 文件中 导入样式文件,比如:import './style.css'。现在,当该 js 模块运行时,含有 CSS 字符串的 <style> 标签,将被插入到 html 文件的 <head>中。

    • 第三步: 编写 css 文件和修改 js 文件

    在 src 目录中添加 style.css文件

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

    src/style.css

    .hello {
      color: red;
    }
    

    修改 js 文件

      import _ from 'lodash';
    + import './style.css';
    
      function createDomElement() {
        let dom = document.createElement('div');
        dom.innerHTML = _.join(['aicoder', '.com', ' wow'], '');
    +   dom.className = 'hello';
        return dom;
      }
    
      document.body.appendChild(createDomElement());
    

    最后重新打开 dist 目录下的 index.html 看一下文字是否变成了红色的了。

    module 配置补充

    模块(module): 这些选项决定了如何处理项目中的不同类型的模块。

    webpack 模块可以支持如下:

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

    module.noParse

    值的类型: RegExp | [RegExp] | function

    防止 webpack 解析那些任何与给定正则表达式相匹配的文件。忽略的文件中不应该含有 import, require, define 的调用,或任何其他导入机制。忽略大型的 library 可以提高构建性能。

    module.exports = {
      mode: 'devleopment',
      entry: './src/index.js',
      ...
      module: {
        noParse: /jquery|lodash/,
        // 从 webpack 3.0.0 开始,可以使用函数,如下所示
        // noParse: function(content) {
        //   return /jquery|lodash/.test(content);
        // }
      }
      ...
    };
    

    module.rules

    创建模块时,匹配请求的规则数组。这些规则能够修改模块的创建方式。这些规则能够对模块(module)应用 loader,或者修改解析器(parser)。

    module.exports = {
      ...
      module: {
        noParse: /jquery|lodash/,
        rules: [
          {
            test: /.css$/,
            use: ['style-loader', 'css-loader']
          }
        ]
      }
      ...
    };
    

    module Rule

    • Rule 条件详解
      • 字符串:匹配输入必须以提供的字符串开始。是的。目录绝对路径或文件绝对路径。
      • 正则表达式:test 输入值。
      • 函数:调用输入的函数,必须返回一个真值(truthy value)以匹配。
      • 条件数组:至少一个匹配条件。
      • 对象:匹配所有属性。每个属性都有一个定义行为。

    Rule.test

    • { test: Condition }:匹配特定条件。一般是提供一个正则表达式或正则表达式的数组,但这不是强制的。
    module.exports = {
      ...
      module: {
        rules: [
          {
            test: /.css$/,
            use: ['style-loader', 'css-loader']
          }
        ]
      }
      ...
    };
    

    其他的条件比如:

    • { include: Condition }:匹配特定条件。一般是提供一个字符串或者字符串数组,但这不是强制的。
    • { exclude: Condition }:排除特定条件。一般是提供一个字符串或字符串数组,但这不是强制的。
    • { and: [Condition] }:必须匹配数组中的所有条件
    • { or: [Condition] }:匹配数组中任何一个条件
    • { not: [Condition] }:必须排除这个条件
    module.exports = {
      ...
      module: {
        rules: [
          {
            test: /.css$/,
            include: [
              path.resolve(__dirname, "app/styles"),
              path.resolve(__dirname, "vendor/styles")
            ],
            use: ['style-loader', 'css-loader']
          }
        ]
      }
      ...
    };
    

    Rule.use

    应用于模块指定使用一个 loader。

    Loaders can be chained by passing multiple loaders, which will be applied from right to left (last to first configured).

    加载器可以链式传递,从右向左进行应用到模块上。

    use: [
      'style-loader',
      {
        loader: 'css-loader'
      },
      {
        loader: 'less-loader',
        options: {
          noIeCompat: true
        }
      }
    ];
    

    传递字符串(如:use: [ "style-loader" ])是 loader 属性的简写方式(如:use: [ { loader: "style-loader "} ])。

    加载 Sass 文件

    加载 Sass 需要sass-loader

    安装

    npm install sass-loader node-sass webpack --save-dev
    

    使用:

    // webpack.config.js
    module.exports = {
      ...
      module: {
        rules: [{
          test: /.scss$/,
          use: [{
            loader: "style-loader"
          }, {
            loader: "css-loader"
          }, {
            loader: "sass-loader"
          }]
        }]
      }
    };
    

    为 sass 文件注入内容:

    如果你要将 Sass 代码放在实际的入口文件(entry file)之前,可以设置 data 选项。此时 sass-loader 不会覆盖 data 选项,只会将它拼接在入口文件的内容之前。

    {
        loader: "sass-loader",
        options: {
            data: "$env: " + process.env.NODE_ENV + ";"
        }
    }
    

    注意:由于代码注入, 会破坏整个入口文件的 source map。 通常一个简单的解决方案是,多个 Sass 文件入口。

    创建 Source Map

    css-loadersass-loader都可以通过该 options 设置启用 sourcemap。

    // webpack.config.js
    module.exports = {
      ...
      module: {
        rules: [{
          test: /.scss$/,
          use: [{
            loader: "style-loader"
          }, {
            loader: "css-loader",
            options: {
              sourceMap: true
            }
          }, {
            loader: "sass-loader",
            options: {
              sourceMap: true
            }
          }]
        }]
      }
    };
    

    PostCSS 处理 loader(附带:添加 css3 前缀)

    PostCSS是一个 CSS 的预处理工具,可以帮助我们:给 CSS3 的属性添加前缀,样式格式校验(stylelint),提前使用 css 的新特性比如:表格布局,更重要的是可以实现 CSS 的模块化,防止 CSS 样式冲突。

    我们常用的就是使用 PostCSS 进行添加前缀,以此为例:

    安装

    npm i -D postcss-loader
    npm install autoprefixer --save-dev
    
    # 以下可以不用安装
    # cssnext可以让你写CSS4的语言,并能配合autoprefixer进行浏览器兼容的不全,而且还支持嵌套语法
    $ npm install postcss-cssnext --save-dev
    
    # 类似scss的语法,实际上如果只是想用嵌套的话有cssnext就够了
    $ npm install precss --save-dev
    
    # 在@import css文件的时候让webpack监听并编译
    $ npm install postcss-import --save-dev
    
    const path = require('path');
    const MiniCssExtractPlugin = require('mini-css-extract-plugin');
    module.exports = {
      mode: 'development',
      entry: './src/index.js',
      output: {
        filename: 'main.js',
        path: path.resolve(__dirname, './dist')
      },
      module: {
        rules: [
          {
            test: /.(sa|sc|c)ss$/,
            use: [
              'style-loader',
              {
                loader: 'css-loader',
                options: {
                  sourceMap: true
                }
              },
              {
                loader: 'postcss-loader',
                options: {
                  ident: 'postcss',
                  sourceMap: true,
                  plugins: loader => [
                    require('autoprefixer')({ browsers: ['> 0.15% in CN'] }) // 添加前缀
                  ]
                }
              },
              {
                loader: 'sass-loader',
                options: {
                  sourceMap: true
                }
              }
            ]
          }
        ]
      }
    };
    

    样式表抽离成专门的单独文件并且设置版本号

    首先以下的 css 的处理我们都把 mode 设置为 production

    webpack4 开始使用: mini-css-extract-plugin插件, 1-3 的版本可以用: extract-text-webpack-plugin

    抽取了样式,就不能再用 style-loader注入到 html 中了。

    npm install --save-dev mini-css-extract-plugin
    
    const path = require('path');
    const MiniCssExtractPlugin = require('mini-css-extract-plugin');
    const devMode = process.env.NODE_ENV !== 'production'; // 判断当前环境是开发环境还是 部署环境,主要是 mode属性的设置值。
    
    module.exports = {
      mode: 'development',
      entry: './src/index.js',
      output: {
        filename: 'main.js',
        path: path.resolve(__dirname, './dist')
      },
      module: {
        rules: [
          {
            test: /.(sa|sc|c)ss$/,
            use: [
              MiniCssExtractPlugin.loader,
              'css-loader',
              'postcss-loader',
              'sass-loader'
            ]
          }
        ]
      },
      plugins: [
        new MiniCssExtractPlugin({
          filename: devMode ? '[name].css' : '[name].[hash].css', // 设置最终输出的文件名
          chunkFilename: devMode ? '[id].css' : '[id].[hash].css'
        })
      ]
    };
    

    再次运行打包:

    在 dist 目录中已经把 css 抽取到单独的一个 css 文件中了。修改 html,引入此 css 就能看到结果了。

    压缩 CSS

    webpack5 貌似会内置 css 的压缩,webpack4 可以自己设置一个插件即可。

    压缩 css 插件:optimize-css-assets-webpack-plugin

    安装

    npm i -D optimize-css-assets-webpack-plugin
    
    const path = require('path');
    const MiniCssExtractPlugin = require('mini-css-extract-plugin');
    const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
    const autoprefixer = require('autoprefixer');
    
    module.exports = {
      mode: 'production',
      entry: './src/index.js',
      output: {
        filename: 'main.[hash].js',
        path: path.resolve(__dirname, './dist')
      },
      module: {
        rules: [
          {
            test: /.(sa|sc|c)ss$/,
            use: [
              MiniCssExtractPlugin.loader,
              {
                loader: 'css-loader'
              },
              {
                loader: 'postcss-loader',
                options: {
                  ident: 'postcss',
                  plugins: loader => [autoprefixer({ browsers: ['> 0.15% in CN'] })]
                }
              },
              {
                loader: 'sass-loader'
              }
            ]
          }
        ]
      },
      plugins: [
        new MiniCssExtractPlugin({
          filename: '[name][hash].css',
          chunkFilename: '[id][hash].css'
        })
      ],
      optimization: {
        minimizer: [new OptimizeCSSAssetsPlugin({})]
      }
    };
    

    JS 压缩

    压缩需要一个插件: uglifyjs-webpack-plugin, 此插件需要一个前提就是:mode: 'production'.

    安装

    npm i -D uglifyjs-webpack-plugin
    
    const path = require('path');
    const MiniCssExtractPlugin = require('mini-css-extract-plugin');
    const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
    const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
    const autoprefixer = require('autoprefixer');
    
    module.exports = {
      mode: 'production',
      entry: './src/index.js',
      output: {
        filename: 'main.[hash].js',
        path: path.resolve(__dirname, './dist')
      },
      module: {
        rules: [
          {
            test: /.(sa|sc|c)ss$/,
            use: [
              MiniCssExtractPlugin.loader,
              {
                loader: 'css-loader'
              },
              {
                loader: 'postcss-loader',
                options: {
                  ident: 'postcss',
                  plugins: loader => [autoprefixer({ browsers: ['> 0.15% in CN'] })]
                }
              },
              {
                loader: 'sass-loader'
              }
            ]
          }
        ]
      },
      plugins: [
        new MiniCssExtractPlugin({
          filename: '[name][hash].css',
          chunkFilename: '[id][hash].css'
        })
      ],
      optimization: {
        minimizer: [
          new UglifyJsPlugin({
            cache: true,
            parallel: true,
            sourceMap: true // set to true if you want JS source maps
          }),
          new OptimizeCSSAssetsPlugin({})
        ]
      }
    };
    

    解决 CSS 文件或者 JS 文件名字哈希变化的问题

    HtmlWebpackPlugin插件,可以把打包后的 CSS 或者 JS 文件引用直接注入到 HTML 模板中,这样就不用每次手动修改文件引用了。

    安装

    npm install --save-dev html-webpack-plugin
    
    const path = require('path');
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    const MiniCssExtractPlugin = require('mini-css-extract-plugin');
    const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
    const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
    const autoprefixer = require('autoprefixer');
    
    module.exports = {
      mode: 'production',
      entry: './src/index.js',
      output: {
        filename: 'main.[hash].js',
        path: path.resolve(__dirname, './dist')
      },
      module: {
        rules: [
          {
            test: /.(sa|sc|c)ss$/,
            use: [
              MiniCssExtractPlugin.loader,
              {
                loader: 'css-loader'
              },
              {
                loader: 'postcss-loader',
                options: {
                  ident: 'postcss',
                  plugins: loader => [autoprefixer({ browsers: ['> 0.15% in CN'] })]
                }
              },
              {
                loader: 'sass-loader'
              }
            ]
          }
        ]
      },
      plugins: [
        new MiniCssExtractPlugin({
          filename: '[name][hash].css',
          chunkFilename: '[id][hash].css'
        }),
        new HtmlWebpackPlugin({
          title: 'AICODER 全栈线下实习', // 默认值:Webpack App
          filename: 'main.html', // 默认值: 'index.html'
          template: path.resolve(__dirname, 'src/index.html'),
          minify: {
            collapseWhitespace: true,
            removeComments: true,
            removeAttributeQuotes: true // 移除属性的引号
          }
        })
      ],
      optimization: {
        minimizer: [
          new UglifyJsPlugin({
            cache: true,
            parallel: true,
            sourceMap: true // set to true if you want JS source maps
          }),
          new OptimizeCSSAssetsPlugin({})
        ]
      }
    };
    

    清理 dist 目录

    每次构建,我们的 /dist 文件夹都会保存生成的文件,然后就会非常杂乱。

    通常,在每次构建前清理 /dist 文件夹,是比较推荐的做法

    clean-webpack-plugin 是一个比较普及的管理插件,让我们安装和配置下。

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

    webpack.config.js

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

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

    加载图片与图片优化

    在 css 文件或者 sass 文件中添加如下代码

    $red: #900;
    $size: 20px;
    
    .box {
      height: 30px*2;
      font-size: $size;
      transform: translate3d( 0, 0, 0 );
    + background: url('../static/1.jpeg')
    }
    

    运行打包发现如下错误:

    ERROR in ./src/static/1.jpeg 1:0
    Module parse failed: Unexpected character '�' (1:0)
    You may need an appropriate loader to handle this file type.
    

    解决方案:file-loader处理文件的导入

    npm install --save-dev file-loader
    

    webpack.config.js

      const path = require('path');
    
      module.exports = {
        entry: './src/index.js',
        output: {
          filename: 'bundle.js',
          path: path.resolve(__dirname, 'dist')
        },
        module: {
          rules: [
            {
              test: /.css$/,
              use: [
                'style-loader',
                'css-loader'
              ]
            },
    +       {
    +         test: /.(png|svg|jpg|gif)$/,
    +         use: [
    +           'file-loader'
    +         ]
    +       }
          ]
        }
      };
    

    此时运行打包,发现 dist 目录多了一个图片文件,另外报错不再出现。

    那更进一步,图片如何进行优化呢?

    image-webpack-loader可以帮助我们对图片进行压缩和优化。

    npm install image-webpack-loader --save-dev
    

    使用:webpack.config.js

      const path = require('path');
    
      module.exports = {
        entry: './src/index.js',
        output: {
          filename: 'bundle.js',
          path: path.resolve(__dirname, 'dist')
        },
        module: {
          rules: [
            {
              test: /.css$/,
              use: [
                'style-loader',
                'css-loader'
              ]
            },
            {
              test: /.(png|svg|jpg|gif|jpeg|ico)$/,
              use: [
                'file-loader',
    +           {
    +             loader: 'image-webpack-loader',
    +             options: {
    +               mozjpeg: {
    +                 progressive: true,
    +                 quality: 65
    +               },
    +               optipng: {
    +                 enabled: false,
    +               },
    +               pngquant: {
    +                 quality: '65-90',
    +                 speed: 4
    +               },
    +               gifsicle: {
    +                 interlaced: false,
    +               },
    +               webp: {
    +                 quality: 75
    +               }
    +             }
    +           },
              ]
            }
          ]
        }
      };
    

    此时在运行 webpack,发现会 生成的图片的大小会被压缩很多。

    更进一步处理图片成 base64

    url-loader功能类似于 file-loader,可以把 url 地址对应的文件,打包成 base64 的 DataURL,提高访问的效率。

    如何使用:

    npm install --save-dev url-loader
    

    webpack.config.js

    module.exports = {
      module: {
        rules: [
          {
            test: /.(png|svg|jpg|gif|jpeg|ico|woff|woff2|eot|ttf|otf)$/,
            use: [
              {
                loader: 'url-loader', // 根据图片大小,把图片优化成base64
                options: {
                  limit: 10000
                }
              },
              {
                loader: 'image-webpack-loader', // 先进行图片优化
                options: {
                  mozjpeg: {
                    progressive: true,
                    quality: 65
                  },
                  optipng: {
                    enabled: false
                  },
                  pngquant: {
                    quality: '65-90',
                    speed: 4
                  },
                  gifsicle: {
                    interlaced: false
                  },
                  webp: {
                    quality: 75
                  }
                }
              }
            ]
          }
        ]
      }
    };
    

    字体的处理(同图片)

    由于 css 中可能引用到自定义的字体,处理也是跟图片一致。

    const path = require('path');
    
      module.exports = {
        entry: './src/index.js',
        output: {
          filename: 'bundle.js',
          path: path.resolve(__dirname, 'dist')
        },
        module: {
          rules: [
            {
              test: /.css$/,
              use: [
                'style-loader',
                'css-loader'
              ]
            },
            {
              test: /.(png|svg|jpg|gif)$/,
              use: [
                'file-loader'
              ]
            },
    +       {
    +         test: /.(woff|woff2|eot|ttf|otf)$/,
    +         use: [
    +           'file-loader'
    +         ]
    +       }
          ]
        }
      };
    

    开发相关辅助

    js 使用 source map

    当 webpack 打包源代码时,可能会很难追踪到错误和警告在源代码中的原始位置。例如,如果将三个源文件(a.js, b.js 和 c.js)打包到一个 bundle(bundle.js)中,而其中一个源文件包含一个错误,那么堆栈跟踪就会简单地指向到 bundle.js。

    使用 inline-source-map 选项,这有助于解释说明 js 原始出错的位置。(不要用于生产环境):

    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'
        },
    +   devtool: 'inline-source-map',
        plugins: [
          new CleanWebpackPlugin(['dist']),
          new HtmlWebpackPlugin({
            title: 'Development'
          })
        ],
        output: {
          filename: '[name].bundle.js',
          path: path.resolve(__dirname, 'dist')
        }
      };
    
     
    inline-source-map

    监控文件变化,自动编译。使用观察模式

    每次修改完毕后,都手动编译异常痛苦。最简单解决的办法就是启动watch

    npx webpack --watch
    

    当然可以添加到 npm 的 script 中

    package.json

    {
        "name": "development",
        "version": "1.0.0",
        "description": "",
        "main": "webpack.config.js",
        "scripts": {
          "test": "echo "Error: no test specified" && exit 1",
    +     "watch": "npx webpack --watch",
          "build": "npx webpack"
        },
        "devDependencies": {
          "clean-webpack-plugin": "^0.1.16",
          "css-loader": "^0.28.4",
          "csv-loader": "^2.1.1",
          "file-loader": "^0.11.2",
          "html-webpack-plugin": "^2.29.0",
          "style-loader": "^0.18.2",
          "webpack": "^3.0.0",
          "xml-loader": "^1.2.1"
        }
      }
    

    但是有个 bug,就是每次我们修改 js 或者 css 文件后,要看到修改后的 html 的变化,需要我自己重新刷新页面。

    如何能不刷新页面,自动更新变化呢?

    使用 webpack-dev-server 和热更新

    webpack-dev-server 为你提供了一个简单的 web 服务器,并且能够实时重新加载(live reloading)。

    安装

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

    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'
        },
        devtool: 'inline-source-map',
    +   devServer: {
    +     contentBase: './dist'
    +   },
        plugins: [
          new CleanWebpackPlugin(['dist']),
          new HtmlWebpackPlugin({
            title: 'Development'
          })
        ],
        output: {
          filename: '[name].bundle.js',
          path: path.resolve(__dirname, 'dist')
        }
      };
    

    启动此 webserver:

    webpack-dev-server --open
    

    官网其他配置

    devServer: {
      clientLogLevel: 'warning', // 可能的值有 none, error, warning 或者 info(默认值)
      hot: true,  // 启用 webpack 的模块热替换特性, 这个需要配合: webpack.HotModuleReplacementPlugin插件
      contentBase:  path.join(__dirname, "dist"), // 告诉服务器从哪里提供内容, 默认情况下,将使用当前工作目录作为提供内容的目录
      compress: true, // 一切服务都启用gzip 压缩
      host: '0.0.0.0', // 指定使用一个 host。默认是 localhost。如果你希望服务器外部可访问 0.0.0.0
      port: 8080, // 端口
      open: true, // 是否打开浏览器
      overlay: {  // 出现错误或者警告的时候,是否覆盖页面线上错误消息。
        warnings: true,
        errors: true
      },
      publicPath: '/', // 此路径下的打包文件可在浏览器中访问。
      proxy: {  // 设置代理
        "/api": {  // 访问api开头的请求,会跳转到  下面的target配置
          target: "http://192.168.0.102:8080",
          pathRewrite: {"^/api" : "/mockjsdata/5/api"}
        }
      },
      quiet: true, // necessary for FriendlyErrorsPlugin. 启用 quiet 后,除了初始启动信息之外的任何内容都不会被打印到控制台。这也意味着来自 webpack 的错误或警告在控制台不可见。
      watchOptions: { // 监视文件相关的控制选项
        poll: true,   // webpack 使用文件系统(file system)获取文件改动的通知。在某些情况下,不会正常工作。例如,当使用 Network File System (NFS) 时。Vagrant 也有很多问题。在这些情况下,请使用轮询. poll: true。当然 poll也可以设置成毫秒数,比如:  poll: 1000
        ignored: /node_modules/, // 忽略监控的文件夹,正则
        aggregateTimeout: 300 // 默认值,当第一个文件更改,会在重新构建前增加延迟
      }
    }
    

    如何启用热更新呢?

    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'
        },
        devtool: 'inline-source-map',
        devServer: {
          contentBase: './dist',
    +     hot: true
        },
        plugins: [
          new CleanWebpackPlugin(['dist']),
          new HtmlWebpackPlugin({
            title: 'Hot Module Replacement'
          }),
    +     new webpack.NamedModulesPlugin(),  // 更容易查看(patch)的依赖
    +     new webpack.HotModuleReplacementPlugin()  // 替换插件
        ],
        output: {
          filename: '[name].bundle.js',
          path: path.resolve(__dirname, 'dist')
        }
      };
    

    JS启用babel转码

    虽然现代的浏览器已经兼容了96%以上的ES6的语法了,但是为了兼容老式的浏览器(IE8、9)我们需要把最新的ES6的语法转成ES5的。那么babel的loader就出场了。

    安装

    npm i -D babel-loader babel-core babel-preset-env
    

    用法

    在webpack的配置文件中,添加js的处理模块。

    module: {
      rules: [
        {
          test: /.js$/,
          exclude: /(node_modules)/,  // 加快编译速度,不包含node_modules文件夹内容
          use: {
            loader: 'babel-loader'
          }
        }
      ]
    }
    

    然后,在项目根目录下,添加babel的配置文件 .babelrc.

    .babelrc文件如下:

    {
      "presets": ["env"]
    }
    

    最后,在入口js文件中,添加ES6的❤新语法:

    class Temp {
      show() {
        console.log('this.Age :', this.Age);
      }
      get Age() {
        return this._age;
      }
      set Age(val) {
        this._age = val + 1;
      }
    }
    
    let t = new Temp();
    t.Age = 19;
    
    t.show();
    

    最后打包:

    npx webpack
    

    最终打包后的js代码:

    var a = 1,
        b = 3,
        c = 9;
    
    console.log('a :', a);
    console.log('b :', b);
    console.log('c :', c);
    
    var Temp = function () {
      function Temp() {
        _classCallCheck(this, Temp);
      }
    
      _createClass(Temp, [{
        key: 'show',
        value: function show() {
          console.log('this.Age :', this.Age);
        }
      }, {
        key: 'Age',
        get: function get() {
          return this._age;
        },
        set: function set(val) {
          this._age = val + 1;
        }
      }]);
    
      return Temp;
    }();
    
    var t = new Temp();
    t.Age = 19;
    
    t.show();
    

    Babel优化

    babel-loader可以配置如下几个options:

    • cacheDirectory:默认值为 false。当有设置时,指定的目录将用来缓存 loader 的执行结果。之后的 webpack 构建,将会尝试读取缓存,来避免在每次执行时,可能产生的、高性能消耗的 Babel 重新编译过程(recompilation process)。如果设置了一个空值 (loader: 'babel-loader?cacheDirectory') 或者 true (loader: babel-loader?cacheDirectory=true),loader 将使用默认的缓存目录 node_modules/.cache/babel-loader,如果在任何根目录下都没有找到 node_modules 目录,将会降级回退到操作系统默认的临时文件目录。

    • cacheIdentifier:默认是一个由 babel-core 版本号,babel-loader 版本号,.babelrc 文件内容(存在的情况下),环境变量 BABEL_ENV 的值(没有时降级到 NODE_ENV)组成的字符串。可以设置为一个自定义的值,在 identifier 改变后,强制缓存失效。

    • forceEnv:默认将解析 BABEL_ENV 然后是 NODE_ENV。允许你在 loader 级别上覆盖 BABEL_ENV/NODE_ENV。对有不同 babel 配置的,客户端和服务端同构应用非常有用。

    注意:sourceMap 选项是被忽略的。当 webpack 配置了 sourceMap 时(通过 devtool 配置选项),将会自动生成 sourceMap。

    babel 在每个文件都插入了辅助代码,使代码体积过大.babel 对一些公共方法使用了非常小的辅助代码,比如 _extend。 默认情况下会被添加到每一个需要它的文件中。你可以引入 babel runtime 作为一个独立模块,来避免重复引入。

    安装:

    npm install babel-plugin-transform-runtime --save-dev
    npm install babel-runtime --save
    

    配置:

    webpack.config.js

    rules: [
      // 'transform-runtime' 插件告诉 babel 要引用 runtime 来代替注入。
      {
        test: /.js$/,
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
        }
      }
    ]
    

    修改.babelrc

    {
      "presets": ["env"],
      "plugins": [
        ["transform-runtime", {
          "helpers": true,
          "polyfill": true,
          "regenerator": true,
          "moduleName": "babel-runtime"
        }]
      ]
    }
    

    此时,webpack打包的时候,会自动优化重复引入公共方法的问题。

    ESLint校验代码格式规范

    安装

    npm install eslint --save-dev
    npm install eslint-loader --save-dev
    
    # 以下是用到的额外的需要安装的eslint的解释器、校验规则等
    npm i -D babel-eslint standard
    

    使用

    // webpack.config.js
    module.exports = {
      // ...
      module: {
        rules: [
          {
            test: /.js$/,
            exclude: /node_modules/,
            loader: "eslint-loader",
            options: {
              // eslint options (if necessary)
              fix: true
            }
          },
        ],
      },
      // ...
    }
    

    eslint配置可以直接放到webpack的配置文件中,也可以直接放到项目根目录的 .eslintrc文档

    // .eslintrc.js
    // https://eslint.org/docs/user-guide/configuring
    module.exports = {
      root: true,
      parserOptions: {
        parser: 'babel-eslint'
      },
      env: {
        browser: true
      },
      extends: [
        // https://github.com/standard/standard/blob/master/docs/RULES-en.md
        'standard'
      ],
      globals: {
        NODE_ENV: false
      },
      rules: {
        // allow async-await
        'generator-star-spacing': 'off',
        // allow debugger during development
        'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
        // 添加,分号必须
        semi: ['error', 'always'],
        'no-unexpected-multiline': 'off',
        'space-before-function-paren': ['error', 'never'],
        // 'quotes': ["error", "double", { "avoidEscape": true }]
        quotes: [
          'error',
          'single',
          {
            avoidEscape: true
          }
        ]
      }
    };
    

    此时eslint的配置就结束了。

    到此为止,一个完整的开发阶段的webpack的配置文件

    const path = require('path');
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    const MiniCssExtractPlugin = require("mini-css-extract-plugin");
    const CleanWebpackPlugin = require('clean-webpack-plugin');
    const autoprefixer = require('autoprefixer');
    const webpack = require('webpack');
    
    module.exports = {
      mode: 'development',
      entry: './src/index.js',
      output: {
        filename: 'main.js',
        path: path.resolve(__dirname, './dist')
      },
      devtool: 'inline-source-map',
      devServer: {
        clientLogLevel: 'warning', // 可能的值有 none, error, warning 或者 info(默认值)
        hot: true, // 启用 webpack 的模块热替换特性, 这个需要配合: webpack.HotModuleReplacementPlugin插件
        contentBase: path.join(__dirname, "dist"), // 告诉服务器从哪里提供内容, 默认情况下,将使用当前工作目录作为提供内容的目录
        compress: true, // 一切服务都启用gzip 压缩
        host: '0.0.0.0', // 指定使用一个 host。默认是 localhost。如果你希望服务器外部可访问 0.0.0.0
        port: 8085, // 端口
        open: true, // 是否打开浏览器
        overlay: { // 出现错误或者警告的时候,是否覆盖页面线上错误消息。
          warnings: true,
          errors: true
        },
        publicPath: '/', // 此路径下的打包文件可在浏览器中访问。
        proxy: { // 设置代理
          "/api": { // 访问api开头的请求,会跳转到  下面的target配置
            target: "http://192.168.0.102:8080",
            pathRewrite: {
              "^/api": "/mockjsdata/5/api"
            }
          }
        },
        quiet: true, // necessary for FriendlyErrorsPlugin. 启用 quiet 后,除了初始启动信息之外的任何内容都不会被打印到控制台。这也意味着来自 webpack 的错误或警告在控制台不可见。
        watchOptions: { // 监视文件相关的控制选项
          poll: true, // webpack 使用文件系统(file system)获取文件改动的通知。在某些情况下,不会正常工作。例如,当使用 Network File System (NFS) 时。Vagrant 也有很多问题。在这些情况下,请使用轮询. poll: true。当然 poll也可以设置成毫秒数,比如:  poll: 1000
          ignored: /node_modules/, // 忽略监控的文件夹,正则
          aggregateTimeout: 300 // 默认值,当第一个文件更改,会在重新构建前增加延迟
        }
      },
      module: {
        rules: [
          {
            test: /.js$/,
            exclude: /(node_modules)/, // 加快编译速度,不包含node_modules文件夹内容
            use: [{
              loader: 'babel-loader'
            },{
              loader: 'eslint-loader',
              options: {
                fix: true
              }
            }]
          },
          {
            test: /.(sa|sc|c)ss$/,
            use: [
              'style-loader', {
                loader: 'css-loader',
                options: {
                  sourceMap: true
                }
              }, {
                loader: 'postcss-loader',
                options: {
                  ident: 'postcss',
                  sourceMap: true,
                  plugins: (loader) => [autoprefixer({browsers: ['> 0.15% in CN']})]
                }
              }, {
                loader: 'sass-loader',
                options: {
                  sourceMap: true
                }
              }
            ]
          }, {
            test: /.(woff|woff2|eot|ttf|otf)$/,
            use: [
              {
                loader: 'url-loader',
                options: {
                  limit: 10000
                }
              }
            ]
          }, {
            test: /.(png|svg|jpg|gif|jpeg|ico)$/,
            use: [
              {
                loader: 'url-loader',
                options: {
                  limit: 10000
                }
              }, {
                loader: 'image-webpack-loader',
                options: {
                  mozjpeg: {
                    progressive: true,
                    quality: 65
                  },
                  optipng: {
                    enabled: false
                  },
                  pngquant: {
                    quality: '65-90',
                    speed: 4
                  },
                  gifsicle: {
                    interlaced: false
                  },
                  webp: {
                    quality: 75
                  }
                }
              }
            ]
          }
        ]
      },
      plugins: [
        new MiniCssExtractPlugin({filename: '[name].css', chunkFilename: '[id].css'}),
        new CleanWebpackPlugin(['dist']),
        new webpack.NamedModulesPlugin(), // 更容易查看(patch)的依赖
        new webpack.HotModuleReplacementPlugin(), // 替换插件
        new HtmlWebpackPlugin({
          title: 'AICODER 全栈线下实习', // 默认值:Webpack App
          filename: 'index.html', // 默认值: 'index.html'
          minify: {
            collapseWhitespace: true,
            removeComments: true,
            removeAttributeQuotes: true, // 移除属性的引号
          },
          template: path.resolve(__dirname, 'src/index.html')
        })
      ],
      optimization: {}
    };
    

    用于生产环境的配置

    const path = require('path');
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    const MiniCssExtractPlugin = require("mini-css-extract-plugin");
    const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
    const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
    const CleanWebpackPlugin = require('clean-webpack-plugin');
    const autoprefixer = require('autoprefixer');
    
    module.exports = {
      mode: 'production',
      entry: './src/index.js',
      output: {
        filename: 'main.[hash].js',
        path: path.resolve(__dirname, './dist')
      },
      module: {
        rules: [
          {
            test: /.js$/,
            exclude: /(node_modules)/, // 加快编译速度,不包含node_modules文件夹内容
            use: [{
              loader: 'babel-loader'
            },{
              loader: 'eslint-loader',
              options: {
                fix: true
              }
            }]
          },
          {
            test: /.(sa|sc|c)ss$/,
            use: [
              MiniCssExtractPlugin.loader, {
                loader: 'css-loader'
              }, {
                loader: 'postcss-loader',
                options: {
                  ident: 'postcss',
                  plugins: (loader) => [autoprefixer({browsers: ['> 0.15% in CN']})]
                }
              }, {
                loader: 'sass-loader'
              }
            ]
          }, {
            test: /.(woff|woff2|eot|ttf|otf)$/,
            use: [
              {
                loader: 'url-loader',
                options: {
                  limit: 10000
                }
              }
            ]
          }, {
            test: /.(png|svg|jpg|gif|jpeg|ico)$/,
            use: [
              'file-loader', {
                loader: 'image-webpack-loader',
                options: {
                  mozjpeg: {
                    progressive: true,
                    quality: 65
                  },
                  optipng: {
                    enabled: false
                  },
                  pngquant: {
                    quality: '65-90',
                    speed: 4
                  },
                  gifsicle: {
                    interlaced: false
                  },
                  webp: {
                    quality: 75
                  }
                }
              }
            ]
          }
        ]
      },
      plugins: [
        new MiniCssExtractPlugin({filename: '[name][hash].css', chunkFilename: '[id][hash].css'}),
        new CleanWebpackPlugin(['dist']),
        new HtmlWebpackPlugin({
          title: 'AICODER 全栈线下实习', // 默认值:Webpack App
          filename: 'index.html', // 默认值: 'index.html'
          template: path.resolve(__dirname, 'src/index.html'),
          minify: {
            collapseWhitespace: true,
            removeComments: true,
            removeAttributeQuotes: true, // 移除属性的引号
          }
        })
      ],
      optimization: {
        minimizer: [
          new UglifyJsPlugin({
            cache: true, parallel: true, sourceMap: true // set to true if you want JS source maps
          }),
          new OptimizeCSSAssetsPlugin({})
        ]
      }
    };
    

    相关的loader列表

    webpack 可以使用 loader 来预处理文件。这允许你打包除 JavaScript 之外的任何静态资源。你可以使用 Node.js 来很简单地编写自己的 loader。

    文件

    • raw-loader 加载文件原始内容(utf-8)
    • val-loader 将代码作为模块执行,并将 exports 转为 JS 代码
    • url-loader 像 file loader 一样工作,但如果文件小于限制,可以返回 data URL
    • file-loader 将文件发送到输出文件夹,并返回(相对)URL

    JSON

    • json-loader 加载 JSON 文件(默认包含)
    • json5-loader 加载和转译 JSON 5 文件
    • cson-loader 加载和转译 CSON 文件

    转换编译(Transpiling)

    • script-loader 在全局上下文中执行一次 JavaScript 文件(如在 script 标签),不需要解析
    • babel-loader 加载 ES2015+ 代码,然后使用 Babel 转译为 ES5
    • buble-loader 使用 Bublé 加载 ES2015+ 代码,并且将代码转译为 ES5
    • traceur-loader 加载 ES2015+ 代码,然后使用 Traceur 转译为 ES5
    • ts-loader 或 awesome-typescript-loader 像 JavaScript 一样加载 TypeScript 2.0+
    • coffee-loader 像 JavaScript 一样加载 CoffeeScript

    模板(Templating)

    • html-loader 导出 HTML 为字符串,需要引用静态资源
    • pug-loader 加载 Pug 模板并返回一个函数
    • jade-loader 加载 Jade 模板并返回一个函数
    • markdown-loader 将 Markdown 转译为 HTML
    • react-markdown-loader 使用 markdown-parse parser(解析器) 将 Markdown 编译为 React 组件
    • posthtml-loader 使用 PostHTML 加载并转换 HTML 文件
    • handlebars-loader 将 Handlebars 转移为 HTML
    • markup-inline-loader 将内联的 SVG/MathML 文件转换为 HTML。在应用于图标字体,或将 CSS 动画应用于 SVG 时非常有用。

    样式

    • style-loader 将模块的导出作为样式添加到 DOM 中
    • css-loader 解析 CSS 文件后,使用 import 加载,并且返回 CSS 代码
    • less-loader 加载和转译 LESS 文件
    • sass-loader 加载和转译 SASS/SCSS 文件
    • postcss-loader 使用 PostCSS 加载和转译 CSS/SSS 文件
    • stylus-loader 加载和转译 Stylus 文件

    清理和测试(Linting && Testing)

    • mocha-loader 使用 mocha 测试(浏览器/NodeJS)
    • eslint-loader PreLoader,使用 ESLint 清理代码
    • jshint-loader PreLoader,使用 JSHint 清理代码
    • jscs-loader PreLoader,使用 JSCS 检查代码样式
    • coverjs-loader PreLoader,使用 CoverJS 确定测试覆盖率

    框架(Frameworks)

    • vue-loader 加载和转译 Vue 组件
    • polymer-loader 使用选择预处理器(preprocessor)处理,并且 require() 类似一等模块(first-class)的 Web 组件
    • angular2-template-loader 加载和转译 Angular 组件
    • Awesome 更多第三方 loader,查看 awesome-webpack 列表

    other

    webpack还是有很多其他需要学习的内容。 请参考官网,或者研究一下vue-cli的生成的webpack的相关配置,也很值得学习。

    另外其他脚手架生成的相关配置都可以研究一下比如:create-react-appyo

  • 相关阅读:
    jQuery火箭图标返回顶部代码
    jQuery火箭图标返回顶部代码
    jQuery火箭图标返回顶部代码
    jQuery火箭图标返回顶部代码
    jQuery火箭图标返回顶部代码
    jQuery火箭图标返回顶部代码
    jQuery火箭图标返回顶部代码
    jQuery火箭图标返回顶部代码
    别傻傻不知道 == 和 equals 的区别【面试系列】
    Spring Boot(九):定时任务
  • 原文地址:https://www.cnblogs.com/fly_dragon/p/9397919.html
Copyright © 2020-2023  润新知