Webpack4.0入门

1. 介绍

  1. webpack是一个模块打包(module bundler)工具。

A bundler for javascript and friends. Packs many modules into a few bundled assets. Code Splitting allows to load parts for the application on demand. Through "loaders", modules can be CommonJs, AMD, ES6 modules, CSS, Images, JSON, Coffeescript, LESS, ... and you custom stuff.

  1. 大版本变化
    (1) Webpack V1.0.0 - 2014.2.20
    编译打包、HMR(模块热更新)、代码分割、文件处理。
    (2) Webpack V2.2.0 - 2017.1.18
    Tree ShakingES6 modules(无需babel)、动态Import、新的文档。
    (3) Webpack V3.0.0 - 2017.6.19
    Scope Hoisting(作用域提升)、Magic Comments(魔法注释,配合动态import使用)
    (4) Webpack V4.0.0 - 2018.2.25
    支持零配置、开发与生产模式、SplitChunksPlugin替换CommonChunkPlugin

2. 初始化

  1. 创建项目文件夹
    webpack-operate
  2. 创建package.json文件
    ➜ webpack-operate npm init
  3. 设置当前npm项目为私有项目
    package.json中添加"private": true
  4. 安装webpack以及webpack-cli
    ➜ webpack-operate npm i webpack webpack-cli --save-dev

业务逻辑中需要使用的包通过--save方式安装;只有打包过程需要使用的包通过--save-dev方式安装。

  1. 命令介绍
  • 获取Webpack信息
    npm info webpack
  • 安装指定版本Webpack
    npm i webpack@4.16.5
  • 获取全局环境中webpack版本
    webpack -v
  • 获取当前项目中webpack版本
    npx webpack -v

3. 模块打包

  1. 创建打包配置文件
    webpack.dev.config.js
  2. 创建入口文件
    ./src/index.js
  3. 编辑打包配置文件
    webpack.dev.config.js
const path = require('path')

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

'./src/index.js'{"main": './src/index.js'}的简写。其中,mainchunk name
如果希望将脚本打包到dist/js/文件夹中,可以修改filename值为js/main.js。即filename不仅可以设置文件名,还可以设置路径。

  1. 创建打包命令
    package.json文件
"scripts": {
    "bundle": "webpack --config ./webpack.dev.config.js --mode development"
}
  • mode的含义
    mode的值为developmentproduction。当取值为production时,输出文件会被压缩。
  • 打包配置
    如果没有配置文件,则使用默认配置,入口文件为./src/index.js,输出文件为dist/main.js
    如果有./webpack.config.js文件,则默认使用该文件作为配置文件。
    如果使用其它配置文件,则需要通过--config指定配置文件。
  • webpack版本
    如果直接通过webpack命令打包,则是使用全局安装的webpack打包。
    如果通过npx webpack/npm run xxx(scripts内部定义命令),则使用当前项目安装的webpack打包。
➜  webpack-demo webpack -v
3.8.1
➜  webpack-demo npx webpack -v
4.29.6
➜  webpack-demo npm run version
> webpack-demo@1.0.0 ver /Users/nimengwei/Code/mycode/webpack/webpack-demo
> webpack -v
4.29.6

webpack3.X版本不支持零配置(没有配置文件),除非在命令行中指定入口文件和出口文件(webpack entry output)。
webpack3.X版本不支持通过mode定义development(开发模式)或production(生产模式)。

  1. 执行打包命令
    npm run bundle
    此时,生成dist文件夹,内部有打包后文件main.js
➜  webpack-operate npm run bundle                      

> webpack-operate@1.0.0 bundle /Users/nimengwei/Code/mycode/webpack/webpack-operate
> webpack --config ./webpack.dev.config.js --mode development

Hash: 5b90c629ada6fed12c59 //当前打包的唯一标识
Version: webpack 4.29.6 //webpack版本
Time: 75ms  //打包用时
Built at: 04/02/2019 7:29:52 PM
  //输出文件   大小  输入chunks         输入chunks名称
  Asset      Size  Chunks             Chunk Names
main.js  3.77 KiB    main  [emitted]  main
Entrypoint main = main.js
[./src/index.js] 0 bytes {main} [built]

这里的chunk名称可以在entry入口中设置。

4. 清理打包文件

  1. 安装依赖
    ➜ webpack-operate npm i clean-webpack-plugin -D
  2. 编辑打包配置文件
    webpack.dev.config.js
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
  //...
  plugins: [
    new CleanWebpackPlugin(),
  ]
  //...
}
  1. plugin的作用是当打包进行到某一个时刻时,执行某些操作。例如,clean-webpack-plugin就是在打包前清除上次打包生成文件。

5. html文件

5.1 html模板

  1. html-webpack-plugin文档
  2. 安装依赖
    ➜ webpack-operate npm i html-webpack-plugin --save-dev
  3. 创建html模板文件
    ./index.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Webpack操作指南</title>
</head>
<body>
  <h1>Webpack操作指南</h1>
</body>
</html>
  1. 编辑打包配置文件
    webpack.dev.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  //...
  plugins: [
    new HtmlWebpackPlugin({
      template: './index.html'
    })
  ]
  //...
}
  1. 执行打包
    npm run bundle
    此时,打包结束后,打包文件夹中会以./index.html为模板,自动生成一个html文件,并将打包生成的 js文件自动引入到该html文件中。
  2. html-webpack-plugin就是在打包结束时根据指定模板生成html文件并插入打包生成的js文件。
  3. 其它配置项
    new HtmlWebpackPlugin({
      template: './index.html', //模板文件
      chunks: ['app'], //生成html文件添加哪些chunks,默认全部添加
      minify: {
        collapseWhitespace: true //压缩html
      }
    })

5.2 html中引入图片

  1. 准备工作
    配置5.1 html模板
    配置9. 加载图片
  2. 修改./index.html
...
<img src="./assets/controls.png" data-src="./assets/controls.png">
<img src="${require('./assets/controls.png')}" data-src="${require('./assets/controls.png')}">
...
  1. 执行打包
    npm run bundle
    发现第一种图片引入方式无法显示,第二种图片引入方式可以显示。

第二种图片引入方式可以显示的前提是需要在配置中为图片指定 loader(推荐 file-loaderurl-loader)。

  1. 安装依赖
    npm i -D html-loader
  2. 编辑打包配置文件
    webpack.dev.config.js
{
  test: /\.html$/,
  use: [
    {
      loader: "html-loader",
      options: {
        attrs: [':src', ':data-src']
      }
    }
  ]
}
  1. 执行打包
    npm run bundle
    此时第一种图片引入方式可以显示,第二种图片引入方式无法显示。

6. 模块热更新

6.1 webpack --watch

  1. 编辑打包命令
    package.json文件
  "scripts": {
    //...
    "watch": "webpack --watch --config ./webpack.dev.config.js --mode development",
    //...
  }
  1. 执行打包
    npm run watch
    webpack会监听打包文件,当文件内容发生变化时自动重新执行打包操作。
    例如,修改src/index.js文件并保存(webpack会自动重新打包),刷新dist/index.html文件,页面执行修改后的js文件。

webpack --watch命令仅仅实现了自动打包操作,无法自动打开浏览器,无法开启一个本地服务,无法自动刷新。

  1. webpack命令配置参数
    语法:webpack [--config webpack.config.js]
    webpack --json: 以 JSON 格式输出 webpack 的运行结果
    --config: 配置文件的路径
    --progress:打印出编译进度的百分比值
    --watch, -w:观察文件系统的变化
    --color, --colors:开启/关闭控制台的颜色
    --display-reasons:显示模块包含在输出中的原因

6.2 webpack-dev-server

6.2.1 快速开发应用程序

  1. 安装依赖
    ➜ webpack-operate npm i webpack-dev-server --save-dev
  2. 编辑打包配置文件
    webpack.dev.config.js
module.exports = {
  //...
  devServer: {
    open: true, //浏览器自动打开
    port: 9000, //指定端口号
    contentBase: './dist' //启动服务的目录
  }
  //...
}

默认浏览器不自动打开,默认端口为8080

  1. 编辑打包命令
    package.json文件
"scripts": {
    "bundle": "webpack --config ./webpack.dev.config.js --mode development",
    "dev": "webpack-dev-server --config ./webpack.dev.config.js --mode development"
  },

webpack3.X版本不支持--mode development/production设置打包模式。

  1. 执行打包命令
    npm run dev
    此时浏览器自动打开http://localhost:9000,可以看到html-webpack-plugin在打包结束后生成的index.html文件(由于html-webpack-plugin的功能,该文件以./index.html为模板,以./release/bundle.jsJavaScript脚本)。且修改 ./src/index.js文件时,浏览器自动刷新。
  2. webpack-dev-server依赖于webpack-cli。安装webpack-cli会同时安装webpack-dev-server。对于webpack3.X版本来说,使用最新版本的webpack-dev-server会报错,可通过降低版本解决,例如2.9.7版本。

使用webpack-dev-server打包会在内存中生成打包文件,提升打包效率。 并不会实际生成dist文件夹以及打包输出文件。如果需要打包生成文件,仍然需要使用webpack命令。
当前配置虽然实现了代码修改自动打包浏览器自动刷新。但浏览器刷新之前的页面操作无法保存,尚未实现不刷新浏览器在运行时更新所修改模块。

6.2.2 devServer.historyApiFallback

  1. 当使用 HTML5 History API 时,任意的 404 响应都可能需要被替代为 index.html。通过传入以下启用:
historyApiFallback: true
  1. 通过传入一个对象,比如使用 rewrites 这个选项,此行为可进一步地控制:
historyApiFallback: {
  rewrites: [
    { from: /^\/$/, to: '/views/landing.html' },
    { from: /^\/subpage/, to: '/views/subpage.html' },
    { from: /./, to: '/views/404.html' }
  ]
}
  1. 可以通过正则表达式匹配通用参数
historyApiFallback: {
  rewrites: [
    {
      from: /^\/([a-zA-Z0-9]+\/?)([a-zA-Z0-9]+)/,
      to: function(context) {
        console.log(context.match[1], context.match[2])
        return '/' + context.match[1] + context.match[2] + '.html'
      }
    },
  ]
}

6.2.3 代理

  1. 文档
    devserver-proxy
    http-proxy-middleware
  2. 配置代理
    编辑打包配置文件
    webpack.dev.config.js
module.exports = {
  //...
  devServer: {
    open: true, //浏览器自动打开
    port: 9000,
    proxy: {
      '/api': {
        changeOrigin: true, //changes the origin of the host header to the target URL
        target: 'http://localhost:8880'
        //访问hocalhost:9000/api/...时,会被代理到hocalhost:8880/api/...
      }
    }
  }
}

此时,访问hocalhost:9000/api/...时,会被代理到hocalhost:8880/api/...

proxy代理只在开发环境中使用。

  1. 支持https
    默认情况下,不接受运行在 HTTPS 上,且使用了无效证书的后端服务器。如果你想要接受,修改配置如下:
proxy: {
  '/api': {
    target: 'http://localhost:8880',
      secure: false
    }
}

6.3 模块热更新(HMR)

  1. 模块热替换(hot module replacementHMR)是 webpack 提供的最有用的功能之一。它允许在运行时更新所有类型的模块,而无需完全刷新。

模块热更新为无刷新浏览器更新。

  1. 编辑打包配置文件
    webpack.dev.config.js
  //...
  devServer: {
    open: true, //浏览器自动打开
    port: 9000,
    contentBase: './dist',
    hot: true,
    hotOnly: true,
  }
  //...
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    //...
  ]
  //...

hotOnlytrue,表示当html文件失效时,不自动刷新浏览器。

  1. 执行打包命令
    npm run dev
    此时实现了CSS文件的模块热更新。
  2. 监听js文件变化回调
if (module.hot) {
   module.hot.accept('./print.js', function() {
     console.log('Accepting the updated printMe module!');
    printMe();
   })
 }

./print.js为当前打包中引入的模块。
js模块热更新需要module.hot.accept监听文件变化的方式实现。
css-loader已经帮我们做了module.hot.accept监听CSS文件变化的代码。
reactvue项目开发时,第三方loader也已经实现了module.hot.accept监听。

6.4 自定义打包脚本

  1. 安装依赖
    ➜ webpack-operate npm i express webpack-dev-middleware -D
  2. 编辑打包命令
    package.json文件
  "scripts": {
    //...
    "server": "node server.js"
    //...
  }
  1. 编辑打包配置文件
    webpack.dev.config.js
  output: {
    //...
    publicPath: './',
    //...
  }
  1. 创建并编辑打包脚本
    ./server.js
const express = require('express')
const webpack = require('webpack')
const webpackDevMiddleware = require('webpack-dev-middleware')
const config = require('./webpack.dev.config')

//根据config配置,返回一个编译器
//在node中直接使用Webpack
const complier = webpack(config) 

//创建一个express服务
const app = express();

//使用webpackDevMiddleware中间件
app.use(webpackDevMiddleware(complier, {
  publicPath: config.output.publicPath
}))

//开启服务,监听8080端口
app.listen(8080, () => {
  console.log("server is running!")
})

命令行中使用webpacknode.js中使用webpack

  1. 执行打包命令
    npm run server
    此时,开启了一个本地服务,手动打开http://localhost:8080/地址,可以看到html-webpack-plugin在打包结束后生成的index.html文件。与webpack --watch类似,修改代码后会重新打包,但没有实现自动打开浏览器,没有实现自动刷新。可以通过修改server.js的方式添加这些功能。

7. 开发调试

7.1 devtool

  1. SourceMap
    保存打包前后代码位置信息,方便调试。
  2. 编辑打包配置文件
    webpack.dev.config.js
module.exports = {
  //...
  devtool: 'inline-source-map',
  //...
}
  1. devtool字段值含义
    image.png

source-map:完整映射到原始源代码。
inline: sourceMappingURL指向base64字符串,否则打包生成map文件。
cheap:代码只映射到行信息,只映射业务代码,不映射第三方模块代码。
module: 不仅映射业务代码,而且映射第三方模块代码。
eval:以eval(string)的方式运行代码,打包速度最快。

eval打包速度最快。

  1. devtool举例介绍
    source-map表示完整映射到原始源代码;sourceMappingURL指向打包生成的map文件。
    inline-source-map表示完整映射到原始源代码;sourceMappingURL指向base64字符串。
    inline-cheap-source-map表示代码只映射到行信息;sourceMappingURL指向base64字符串。
    cheap-module-source-map表示代码只映射到行信息;不仅映射业务代码,而且映射第三方模块代码;sourceMappingURL指向打包生成的map文件。
  2. devtool适用环境
    开发环境:evaleval-source-mapcheap-eval-source-mapcheap-module-eval-source-map
    生产环境:source-maphidden-source-mapnosources-source-map
  3. devtool最佳实践
    开发(development)环境:cheap-module-eval-source-map/cheap-module-source-map
    生产(production)环境:source-map

7.2 插件

  1. 插件实现了对 source map 生成,进行更细粒度的控制。它可以替代 devtool 选项。
  2. 文档
    eval-source-map-dev-tool-plugin
    source-map-dev-tool-plugin

7.3 CSS SourceMap

  1. 准备工作
    10. 加载样式
  2. 编辑打包配置文件
    webpack.dev.config.js
{
  test: /\.css$/,
  use: [
    {
      loader: "style-loader",
      options: {
        //singleton: true,
        sourceMap: true
      }
    },
    {
      loader: 'css-loader',
      options: {
        sourceMap: true
      }
    },
    {
      loader: 'postcss-loader',
      options: {
        ident: 'postcss',
        plugins: [ require('autoprefixer')()],
        sourceMap: true
      }
    }
  ]
}
  1. CSS开启SourceMap配置总结
    (1) 关闭style-loader中的singleton选项。
    (2) 处理样式的所有loader(style-loadercss-loaderpostcss-loader)都开启sourceMap选项。

8. 加载脚本

8.1 ES6语法

  1. 安装依赖
    npm i -D babel-loader @babel/core @babel/preset-env

打开babel文档,可以看到在不同工具中使用babel的方法。

  1. 编辑打包配置文件
   //...
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /(node_modules|bower_components)/,
        loader: "babel-loader",
        options: {
          presets: ["@babel/preset-env"],
          plugins: []
        }
      }
    ]
  }
  //...
  1. 执行打包命令
    npm run bundle
    此时生成的app.bundle.js大小为28.8 KB

@babel/preset-env只支持stage-4范围内的JavaScript语法。
stage的包含顺序是:左边包含右边全部特性。
stage-0 > stage-1 > stage-2 > stage-3 > stage-4

8.2 添加polyfill

  1. babel-polyfill文档
  2. babel-polyfill介绍
    Babel默认只转换新的JavaScript句法(syntax),而不转换新的API,比如IteratorGeneratorSetMapsProxyReflectSymbolPromise等全局对象,以及一些定义在全局对象上的方法(比如Object.assign)都不会转码。
    举例来说,ES6Array对象上新增了Array.from方法。Babel就不会转码这个方法。如果想让这个方法运行,必须使用babel-polyfill,为当前环境提供一个垫片。
  3. 安装依赖
    npm install --save @babel/polyfill
    @babel/polyfill依赖安装到dependencies,而不是devDependencies
  4. 编辑入口文件
    src/index.js
import "@babel/polyfill";
//...
  1. 执行打包命令
    npm run bundle
    由于将所有的polyfill都打包输出,此时生成的app.bundle.js大小为534 KB
  2. polyfill按需打包
    (1) 安装依赖
    npm i core-js@2 --save
    (2) 编辑打包配置文件
    webpack.dev.config.js
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: "babel-loader",
        options: {
          presets: [
            [ "@babel/preset-env", {"useBuiltIns": "usage", "corejs": 2}]
          ],
          plugins: []
        }
      }

useBuiltIns可选值为一个布尔值,默认为false。当设置为true时,表示使用浏览器内置的语法,而不是对任意插件进行全量polyfill

(3) 编辑入口文件
src/index.js

//import "@babel/polyfill";
//...

When setting "useBuiltIns": "usage", polyfills are automatically imported when needed. Please remove the import '@babel/polyfill' call or use "useBuiltIns": 'entry' instead.

(4) 执行打包命令
npm run bundle
此时生成的app.bundle.js大小为84.6 KB

  1. 指定项目支持的浏览器版本
    (1) 编辑打包配置文件
    webpack.dev.config.js
{
  test: /\.js$/,
  exclude: /node_modules/,
  loader: "babel-loader",
  options: {
    presets: [
      [
        "@babel/preset-env",
        {
          targets: {
            //browsers: [ ">1%"],
            chrome: "67",
            safari: "11.1"
          },
          "useBuiltIns": "usage",
          "corejs": 2
        }
      ]
    ],
    plugins: []
  }
}

当指定targets浏览器版本时,@babel/preset-env@babel/polyfill只对指定浏览器版本不支持的语法进行转码和添加垫片。

(2) 执行打包命令
npm run bundle
此时生成的app.bundle.js大小为28.7 KB

8.3 transform-runtime

  1. transform-runtime文档
  2. transform-runtimebabel-polyfill对比
    运行环境中并没有实现的APIbabel-polyfill会给其做兼容。 这样做有一个缺点,就是会污染全局变量。不推荐在一些方法类库中去使用。为了不污染全局对象和内置的对象原型,又想使用新的API,就可以使用transform-runtime

transform-runtime可以以闭包的形式实现babel-polyfill的功能,不污染全局变量。src/index.js文件中无需引入import "@babel/polyfill";

  1. 安装依赖
    npm install --save-dev @babel/plugin-transform-runtime
    npm install --save @babel/runtime
    npm install --save @babel/runtime-corejs2
  2. 编辑打包配置文件
    webpack.dev.config.js
{
  test: /\.js$/,
  exclude: /node_modules/,
  loader: "babel-loader",
  options: {
    plugins: [
      [
        "@babel/plugin-transform-runtime",
        {
          "corejs": 2,
          "helpers": true,
          "regenerator": true,
          "useESModules": false
        }
      ]
    ]
  }
}

corejs如果设置为false,则不需要安装@babel/runtime-corejs2

  1. 执行打包命令
    npm run bundle
    此时生成的app.bundle.js大小为116 KiB
  2. 简化打包配置文件
    创建.babelrc文件,并将options值剪切到该文件中。
{
  "plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        "corejs": 2,
        "helpers": true,
        "regenerator": true,
        "useESModules": false
      }
    ]
  ]
}

如果你写的是业务代码,则使用babel-polyfill。如果你写的是第三方类库,则需要使用plugin-transform-runtime

8.4 Stage-0语法支持

  1. 安装依赖
➜  webpack-operate npm i @babel/plugin-proposal-function-bind @babel/plugin-proposal-export-default-from @babel/plugin-proposal-logical-assignment-operaors @babel/plugin-proposal-optional-chaining @babel/plugin-proposal-pipeline-operator @babel/plugin-proposal-nullish-coalescing-operator @babel/plugin-proposal-do-expressions @babel/plugin-proposal-decorators @babel/plugin-proposal-function-sent @babel/plugin-proposal-export-namespace-from @babel/plugin-proposal-numeric-separator @babel/plugin-proposal-throw-expressions @babel/plugin-syntax-dynamic-import @babel/plugin-syntax-import-meta @babel/plugin-proposal-class-properties @babel/plugin-proposal-json-strings -D
  1. 编辑babel配置文件
 "plugins": [
    // Stage 0
    "@babel/plugin-proposal-function-bind",

    // Stage 1
    "@babel/plugin-proposal-export-default-from",
    "@babel/plugin-proposal-logical-assignment-operators",
    ["@babel/plugin-proposal-optional-chaining", { "loose": false }],
    ["@babel/plugin-proposal-pipeline-operator", { "proposal": "minimal" }],
    ["@babel/plugin-proposal-nullish-coalescing-operator", { "loose": false }],
    "@babel/plugin-proposal-do-expressions",

    // Stage 2
    ["@babel/plugin-proposal-decorators", { "legacy": true }],
    "@babel/plugin-proposal-function-sent",
    "@babel/plugin-proposal-export-namespace-from",
    "@babel/plugin-proposal-numeric-separator",
    "@babel/plugin-proposal-throw-expressions",

    // Stage 3
    "@babel/plugin-syntax-dynamic-import",
    "@babel/plugin-syntax-import-meta",
    ["@babel/plugin-proposal-class-properties", { "loose": false }],
    "@babel/plugin-proposal-json-strings"
  ]

Babel 7,Removing Babel's Stage Presets

8.5 React语法

  1. babel-preset-react文档
  2. 安装依赖
    ➜ webpack-operate npm i react react-dom --save
    ➜ webpack-operate npm install --save-dev @babel/preset-react
  3. 编辑babel配置文件
{
  "presets": [
    [
      "@babel/preset-env", {
        "targets": {
          "chrome": "67",
          "safari": "11.1"
        },
        "useBuiltIns": "usage",
        "corejs": 2
      }
    ],
    "@babel/preset-react"
  ]
}

presets的执行顺序为数组从后向前。@babel/preset-env@babel/preset-react的顺序不能变。

  1. 编辑入口文件
    ./src/index.js
import React, { Component } from 'react'
import ReactDom from 'react-dom'

class App extends Component {
  render() {
    return (
      <div>
        Hello world!!!
      </div>
    )
  }
}

ReactDom.render(<App/>, document.getElementById('root'))

9. 加载图片

9.1 file-loader

  1. 安装依赖
    npm install --save-dev file-loader
  2. 编辑打包配置文件
    webpack.dev.config.js
  module: {
    rules: [{
      test: /\.(png|svg|jpg|gif)$/,
      use: [
        'file-loader'
      ]
    }]
  }
  1. 添加图片
    src/assets/weixin.jpg
  2. 修改src/index.js文件
var img = require('./assets/weixin.jpg')

var myLogo = new Image()
myLogo.src = img
document.body.appendChild(myLogo)

图片资源作为img元素。

  1. 修改src/style.css文件
body {
  background: url('./logo.png')
}

图片资源作为元素背景。

  1. 修改图片打包输出路径及文件名
    webpack.dev.config.js
  module: {
    rules: [{
      test: /\.(png|svg|jpg|gif)$/,
      use: {
        loader:'file-loader',
        options: {
          name: '[name]-[hash:5].[ext]',
          outputPath: 'images/'
        }
      }
    }]
  }

file-loader中的placeholders文档。

9.2 url-loader

  1. 安装url-loader
    ➜ webpack-operate npm i url-loader --save-dev
  2. 修改打包配置文件
    webpack.dev.config.js
module: {
    rules: [{
      test: /\.(png|svg|jpg|gif)$/,
      use: {
        loader:'url-loader',
        options: {
          name: '[name]-[hash:5].[ext]',
          outputPath: 'images/'
        }
      }
    }]
  }

url-loader拥有file-loader的全部功能。
url-loader会将图片转化为base64字符串。

  1. 将大于4KB的图片资源打包输出
module: {
    rules: [{
      test: /\.(png|svg|jpg|gif)$/,
      use: {
        loader:'url-loader',
        options: {
          name: '[name]-[hash:5].[ext]',
          outputPath: 'images/',
          limit: 4096
        }
      }
    }]
  }

9.3 其它loader

  1. 图片压缩
    img-loader
  2. 自动合成雪碧图
    postcss-sprites

10. 加载样式

10.1 使用CSS样式

  1. 安装依赖
    npm install --save-dev style-loader css-loader
  2. 编辑打包配置文件
    webpack.dev.config.js
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
              "style-loader",  // 将 JS 字符串生成为 style 节点
              "css-loader",    // 将 CSS 转化成 CommonJS 模块
        ]
      }
    ]
  }

webpackloader数组的执行顺序为从后向前。

  1. 创建src/style.css文件
.img {
  width: 300px;
  height: 300px;
  transform: translate(100px, 100px);
}
  1. 修改src/index.js文件
import './style.css'
var logo = require('./assets/weixin.jpg')

var myLogo = new Image()
myLogo.src = logo
myLogo.classList.add('img')
document.body.appendChild(myLogo)

使用file-loaderurl-loader后才能支持weixin.jpg文件引入。
这种方式引入的CSS文件会被style标签包裹并插入到页面head内部。

  1. 查询参数importLoaders
    用于配置css-loader作用于 @import 的资源之前有多少个loader

10.2 style-loader配置项

  1. style-loader配置项文档
  2. 编辑打包配置文件
      {
        test: /\.css$/,
        use: [
          {
            loader: "style-loader",
            options: {
              insertInto: () => document.querySelector("#root"),//插入到指定DOM
              singleton: true, //是否只使用一个style标签
              transform: './src/css-transform.js'
            }
          },
          'css-loader'
        ]
      }
  1. 创建并编辑变形文件
module.exports = function(css) {
  // Here we can change the original css
  console.log(css);
  if(window.innerWidth > 768) {
    return css.replace('yellow', 'red');
  } else {
    return css.replace('yellow', 'pink');
  }
};

10.3 style-loader/useable

  1. 编辑打包配置文件
    webpack.dev.config.js
  {
    test: /\.css$/,
    use: [
      'style-loader/useable',
      'css-loader'
    ]
  }
  1. 修改src/index.js文件
import base from './style.css'

let flag = true;
setInterval(() => {
  if(flag) {
    base.unuse()
  } else {
    base.use()
  }
  flag = !flag
}, 500);

./style.css样式每500ms在生效和不生效两种状态之间切换一次。

使用style-loader/useable后,样式默认不生效。必须调用use()方法才会生效。

10.4 CSS模块化

  1. 修改webpack.dev.config.js
{
  test: /\.css$/,
  use: [
    {
      loader: 'css-loader',
      options: {
        modules: true,
        localIdentName: '[path][name]__[local]--[hash:base64:5]'
      }
    }
  ]
}
  1. 修改src/index.js文件
import style from './style.css'
var src = require('./assets/weixin.jpg')

var logo1 = new Image()
logo1.src = src
logo1.classList.add(style.img)
document.body.appendChild(logo1)

var logo2 = new Image()
logo2.src = src
logo2.classList.add('img')
document.body.appendChild(logo2)

./style.css并不会成为全局样式。
style.img类会创建一个由[path][name]__[local]--[hash:base64:5]组成的类名,并添加./style.css中的.img样式。
'img'类并不会添加./style.css中的.img样式。

  1. 作用域
    :local(.className) 可以被用来在局部作用域中声明 className。局部的作用域标识符会以模块形式暴露出去。
    :global(.className) 可以用来声明一个明确的全局选择器。

以上配置都是css-loader@2.X版本的配置,css-loader@1.X版本配置@2.X版本配置不同。

10.5 使用SCSS样式

  1. 安装依赖
    npm install sass-loader node-sass --save-dev
  2. 编辑打包配置文件
    webpack.dev.config.js
{
  test: /\.scss$/,
  use: [
    "style-loader", // 将 JS 字符串生成为 style 节点
    "css-loader", // 将 CSS 转化成 CommonJS 模块
    "sass-loader" // 将 Sass 编译成 CSS,默认使用 Node Sass
  ]
}
  1. 创建src/style.scss文件
body {
  img {
    width: 300px;
    height: 300px;
  }
}
  1. 引入src/style.scss文件
import './style.scss'

10.6 添加样式兼容前缀

  1. 安装依赖
    ➜ webpack-operate npm i postcss-loader autoprefixer -D

-D等价与--save-dev

  1. 编辑打包配置文件
    webpack.dev.config.js
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader',
          {
            loader: 'postcss-loader',
            options: {
              ident: 'postcss',
              plugins: [ require('autoprefixer')()]
            }
          }
        ]
      }
    ]
  }

postcss-loader如果与sass-loader同时使用,则postcss-loaderloader数组中位于sass-loader之后。

10.7 使用字体图标

  1. 进入阿里云图标网站
  2. 创建项目并添加图标
  3. 下载Unicode至本地并解压
  4. 拷贝文件
    iconfont.eoticonfont.svgiconfont.ttficonfont.woff文件拷贝到项目中。项目路径如下:
    ./src/font/iconfont.XXX
  5. iconfont.css文件内容拷贝到src/style.css并修改url路径
@font-face {font-family: "iconfont";
  src: url('./font/iconfont.eot?t=1554215262535'); /* IE9 */
  src: url('./font/iconfont.eot?t=1554215262535#iefix') format('embedded-opentype'), /* IE6-IE8 */
  url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAALoAAsAAAAABpQAAAKbAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCCcAqBHIEcATYCJAMICwYABCAFhG0HMxvFBRHVk0tkXx9wcomVY8Ck6KrLNGtjkwhFT/9iuvM1Ch7cCAfnfTw87fft3Dfz/86KGyLaRCuNRKKSNVESGRqRrB2P4om6rwEKuABNzW1ibUkMt+iNRsSTWPbMSH8ks/pynaO0JXqChifQ1nSPpAg5c5zvkrXsITvJd+x8WEbQKUyFsAKgSxAl3/k81+QroAPJD3SOt6qgosQBjvc8QLvsaCAD3hg3jF3gEh4TaDNlQ5xZNJpUkVmrAnE7nQ+pkkvLMqsVoVlzMI/FkUr1qHJ5HP4+fu0KiUojsdou3apPqBNfsZekXb3Sni8IdoyEDWTiWm35giQYm7SVszn21Rx8Xaxr3yv26hLsr7MaO8AEVO5J42pvtVbBbU21iUOjPpfYvNt9hgdX+HwprZvevb4Q0sYkMrwR8lH7b63z+LyxOsJm/44P0PtN9uLlapBnxrYD/u7smF2/dxj+WHCwmMGTRa1+YXbxX60ql7Pck+SViyP1t7rUCdRb9T+Hk/PvXnP2eSl3U+5/6WXw7mVuqO8XRTlDJPhTXdl6i7zAdVa5ymiZHKhs3kFHmzbUULzPl7H2wduqh2JwRLTFZJpLirWMGTIbaGi3B03FcbRZd/54u353JJFbseYeQOjxFJUuL5D0eEtk5jMahvxBU0+waHMjuLDdUoCpNiUMGJ8Q/iCN6ko6Ptai5gvGryKgrC7kHkgqzYGlm8XaEiukObaod2wzS5BUl7AAz2FR1NBQnWHEesLcOIYh696kR3UpNDElDBifEP4gjepK+qNZK33/gvGrCKhloCZ/IKl0drB0swexNFe9Bu5FjXrHNrMESXUJCzAPi6KGpn5ehhHryYhi4xj2MNnXrK9vLb/uGLQJy1F17B1JV3FAXli8UAgA') format('woff2'),
  url('./font/iconfont.woff?t=1554215262535') format('woff'),
  url('./font/iconfont.ttf?t=1554215262535') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
  url('./font/iconfont.svg?t=1554215262535#iconfont') format('svg'); /* iOS 4.1- */
}

:global(.iconfont) {
  font-family: "iconfont" !important;
  font-size: 16px;
  font-style: normal;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

:global(.iconstar_blue:before) {
  content: "\e625";
}

由于css-loader使用了css模块化(modules), 所以这里需要使用:global(.className)声明全局选择器。

  1. 修改src/index.js
import './style.css'

document.body.innerHTML = `<div>
    <span class='iconfont iconstar_blue'></span>
    <span class='iconfont'>&#xe625;</span>
</div>`
  1. 编辑打包配置文件
 {
    test: /\.(eot|ttf|svg|woff)$/,
    use: {
      loader: "file-loader",
      options: {
         name: '[name]-[hash:5].[ext]',
         outputPath: 'font/'
      }
    }
  }

11. Entry与Output配置

11.1 多个输出文件

  1. 创建src/print.js文件
console.log('print')
  1. 修改打包配置文件
    webpack.dev.config.js
  entry: {
    app: './src/index.js',
    print: './src/print.js'
  },
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
  1. 执行打包命令
    npm run dev
    webpack打包输出几个bundle文件并不是由模块依赖树决定,而是根据打包配置文件中的output决定。即使src/index.js文件引入了src/print.js文件,仍会将后者打包输出为单独的bundle

11.2 publicPath配置

  1. 修改打包配置文件
    webpack.dev.config.js
  //...
  output: {
    publicPath: 'http://cdn.com.cn',
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
  //...
  1. 执行打包命令
    npm run dev
    此时,通过html-webpack-plugin插件在dist目录中生成的html文件中引入打包生成的js文件的src会添加http://cdn.com.cn公共路径。
<script type="text/javascript" src="http://cdn.com.cn/app.bundle.js"></script>
<script type="text/javascript" src="http://cdn.com.cn/print.bundle.js"></script>

11.3 浏览器长缓存(Caching)

  1. 提取第三方库的代码、使用contentHash、提取webpack runtime
  2. 在生产环境中。为了利用浏览器缓存并及时更新。我们希望当代码变化时,打包生成的文件名变化。当代码没有变化时,打包生成的文件名不变。
  3. 修改打包配置文件
    webpack.dev.config.js
//...
output: {
    filename: '[name].[contenthash].bundle.js',
    chunkFilename: '[name].[contenthash].chunk.js',
  },
//...

打生产环境包才需要这样配置。
在使用 ExtractTextWebpackPlugin 时,可以用 [contenthash] 来获取提取文件的 hash既不是[hash]也不是[chunkhash])。

  1. webpack 4早期版本中,可能出现代码内容没有变化,但是重新打包contentHash仍然变化的情况。原因是打包输出的js文件之间的关系代码manifest发生变化。可以通过将manifest代码分割的方式解决。
  //...
  optimization: {
    runtimeChunk: {
        name: 'runtime'
    }
  }
  //...

最新版本webpack不需要此项配置。

参考资料

Javascript 设计模式系统讲解与应用
Webpack中文文档

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,784评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,745评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,702评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,229评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,245评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,376评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,798评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,471评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,655评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,485评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,535评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,235评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,793评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,863评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,096评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,654评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,233评论 2 341

推荐阅读更多精彩内容

  • 前言 本文主要从webpack4.x入手,会对平时常用的Webpack配置一一讲解,各个功能点都有对应的详细例子,...
    BetterChen阅读 1,941评论 0 3
  • 版权声明:本文为博主原创文章,未经博主允许不得转载。 webpack介绍和使用 一、webpack介绍 1、由来 ...
    it筱竹阅读 11,025评论 0 21
  • webpack 介绍 webpack 是什么 为什么引入新的打包工具 webpack 核心思想 webpack 安...
    yxsGert阅读 6,433评论 2 71
  • 写在开头 先说说为什么要写这篇文章, 最初的原因是组里的小朋友们看了webpack文档后, 表情都是这样的: (摘...
    Lefter阅读 5,266评论 4 31
  • 保持每场比赛都记录吧,虽然说我很菜,写得不好,但会慢慢变强,也会把博客写好的。 比赛链接 A A题链接 中文题意:...
    不知名小号阅读 327评论 0 0