webpack学习笔记

webpack

  • 功能说明:Webpack 是一个前端资源加载/打包工具。它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。
  • 安装:
    • 1:安装nodejs
    • 2: 升级或者安装cnpm:
      • npm install cnpm -g
    • 3:安装nrm工具
      • cnpm install -g nrm
    • 3:安装 webpack
    • 4:安装客户端
      • npm install webpack-cli -g(npm install webpack --global)
      • npm install --save-dev webpack-cli(npm install webpack webpack-cli --save-dev)
    • 安装说明
      • 上述命令可采用简写,install可简写为i,--global可简写为-g,--save-dev可简写为-D(这个命令是用于把配置添加到package.json的开发环境配置列表中,后面会提到),--save可简写为-S,同时国内我们可以采用cnpm,配置方法可去这里查看,这样安装速度会相对较快。

  • 第一个例子
    • 系统安装与打包
      • 1:新建目录app
      • 2: 创建package.json
        • npm init
      • 3:创建目录src,dist
        • mkdir src  mkdir dist
      • 3:创建新文件dist/index.html
        • lang="en">  charset="UTF-8">  </span><span style="color:#b00000;">Webpack Project</span><span style="color:#000025;">  id='root'>

 src="bundle.js"> 

  • 4:创建新文件src/hello.js
    • module.exports = function() {     let hello = document.createElement('div');     hello.innerHTML = "Long time no see!";     return hello;   };

  • 5: 创建新文件/src/index.js
    • const hello = require('./hello.js'); document.querySelector("#root").appendChild(hello());

      上述操作就相当于我们把hello.js模块合并到了index.js模块,之后我们打包时就只需把index.js模块打包成bundle.js,然后供index.html引用即可,这就是最简单的webpack打包原理。

  • 6:执行编译
    • webpack ./src/index.js --output ./dist/bundle.js
    • 如果编译出错,执行以下命令
      • 全局
        • npm install babel-loader -g
        • npm install babel-core -g
      • 局部
        • nmp install babel-loader -D
        • npm install babel-core -D
    • 到这一步就可以访问index.html
  • 7:通过webpack.config.js配置webpack的出口和入口
    • 写法1:
      • // webpack.config.js module.exports = {     entry: __dirname + "/src/index.js", // 入口文件     output: {         path: __dirname + "/dist", //打包后的文件存放的地方         filename: "bundle.js" //打包后输出文件的文件名     } }

        • 注:__dirname是node.js中的一个全局变量,它指向当前执行脚本所在的目录
    • 写法2:
      • // webpack.config.js const path = require('path'); module.exports = {     entry: path.join(__dirname, "/src/index.js"), // 入口文件     output: {         path: path.join( __dirname, "/dist"), //打包后的文件存放的地方         filename: "bundle.js" //打包后输出文件的文件名     } }

        • 但平时我们看到的脚手架配置也比较喜欢采用node.js的path模块来处理绝对路径,所以我们也可以采用如下的写法
    • 在命令行中输入webpack后打包完成
  • 8:通过package.json进行智能打包
    • 修改package.json
      • 通过命令webpack  -v ,webpack-cli -v查看本地版本
      • 修前配置文件
        • {   "name": "app",   "version": "1.0.0",   "scripts": {     "test": "echo \"Error: no test specified\" && exit 1"   },   "author": "",   "license": "ISC",   "dependencies": {},   "devDependencies": {},   "description": "" }

      • 修改后配置文件
        • {   "name": "app",   "version": "1.0.0",   "scripts": {     "start": "webpack"   },   "author": "",   "license": "ISC",   "dependencies": {},   "devDependencies": {     "webpack": "^4.42.0",     "webpack-cli": "^3.3.11"   },   "description": "" }

    • 执行npm start打包
  • 构建本地服务器
    • webpack-dev-server配置本地服务器
      • 1:cnpm i webpack-dev-server -D
        • dev-server配置选项
          • contentBase:设置服务器所读取文件的目录,当前我们设置为"./dist"
          • port:设置端口号,如果省略,默认为8080
          • inline:设置为true,当源文件改变时会自动刷新页面
          • historyApiFallback:设置为true,所有的跳转将指向index.html
      • 2:修改webpack.config.js
        • // webpack.config.js const path = require('path'); module.exports = {     entry: path.join(__dirname, "/src/index.js"), // 入口文件     output: {         path: path.join( __dirname, "/dist"), //打包后的文件存放的地方         filename: "bundle.js" //打包后输出文件的文件名     },     devServer: {         contentBase: "./dist", // 本地服务器所加载文件的目录         port: "8088",   // 设置端口号为8088         inline: true, // 文件修改后实时刷新         historyApiFallback: true, //不跳转     } }

          如果 出现 错误,重新安装babel,webpack,webpack-cli

          • npm install --save-dev webpack
      • 3: 执行npm run dev,服务启动成功以后,可以通过http://localhost:8088访问服务
    • Source Maps调试配置
      • 1:修改webpack.config.js
        • const path = require('path'); module.exports = {     entry: path.join(__dirname, "/src/index.js"), // 入口文件     output: {         path: path.join( __dirname, "/dist"), //打包后的文件存放的地方         filename: "bundle.js" //打包后输出文件的文件名     },     devServer: {         contentBase: "./dist", // 本地服务器所加载文件的目录         port: "8088",  // 设置端口号为8088         inline: true, // 文件修改后实时刷新         historyApiFallback: true, //不跳转     },     devtool: 'source-map'  // 会生成对于调试的完整的.map文件,但同时也会减慢打包速度 }

          • 新增了devtool组件
      • 2:执行:npm run build
      • 3:查看dist目录
        • bundle.js.map
      • 说明:通过配置,我们会在打包时生成对应于打包文件的.map文件,使得编译后的代码可读性更高,更易于调试。
    • Loaders加载器
      • 说明:loaders是webpack最强大的功能之一,通过不同的loader,webpack有能力调用外部的脚本或工具,实现对不同格式的文件的处理,例如把scss转为css,将ES66、ES7等语法转化为当前浏览器能识别的语法,将JSX转化为js等多项功能

        • Loaders需要单独安装并且需要在webpack.config.js中的modules配置项下进行配置,Loaders的配置包括以下几方面
          • test:一个用以匹配loaders所处理文件的拓展名的正则表达式(必须)
          • loader:loader的名称(必须)
          • include/exclude:手动添加必须处理的文件(文件夹)或屏蔽不需要处理的文件(文件夹)(可选);
          • options:为loaders提供额外的设置选项(可选)

        配置css-loader

        • 1:安装插件
          • cnpm i style-loader css-loader -D
        • 2:向webpack.config.js中添加module配置
          • const path = require('path'); module.exports = {     entry: path.join(__dirname, "/src/index.js"), // 入口文件     output: {         path: path.join( __dirname, "/dist"), //打包后的文件存放的地方         filename: "bundle.js" //打包后输出文件的文件名     },     devServer: {         contentBase: "./dist", // 本地服务器所加载文件的目录         port: "8088",  // 设置端口号为8088         inline: true, // 文件修改后实时刷新         historyApiFallback: true, //不跳转     },     devtool: 'source-map',  // 会生成对于调试的完整的.map文件,但同时也会减慢打包速度     module: {         rules: [             {                 test: /\.css$/,   // 正则匹配以.css结尾的文件                 use: ['style-loader', 'css-loader']  // 需要用的loader,一定是这个顺序,因为调用loader是从右往左编译的             }         ]     } }

        • 3:我们在src文件夹下新建css文件夹,该文件夹内新建style.css文件
          • 文件内容为
            • body {     background: gray; }
        • 4:修改index.js文件
          • import './css/style.css';  //导入css  const hello = require('./hello.js'); document.querySelector("#root").appendChild(hello());

        • 5:执行 npm run dev

        配置sass-loader

        • 1: 安装sass-loader
          • cnpm i sass-loader node-sass -D
        • 2: 为webpack.config.js中的module中添加rule
          • ,             {                 test: /\.(scss|sass)$/,   // 正则匹配以.scss和.sass结尾的文件                 use: ['style-loader', 'css-loader', 'sass-loader']  // 需要用的loader,一定是这个顺序,因为调用loader是从右往左编译的             }

            • 添加后的文件
        • 3:css文件夹中添加 blue.scss
          • /* blue.scss */ $blue: blue; body{     color: $blue; }
        • 4: 执行运行命令
          • npm run dev
  • Babel
    • 说明
      • Babel其实是一个编译JavaScript的平台,它可以编译代码帮你达到以下目的:  让你能使用最新的JavaScript代码(ES6,ES7...),而不用管新标准是否被当前使用的浏览器完全支持; 让你能使用基于JavaScript进行了拓展的语言,比如React的JSX;

    • Babel的安装与配置
      • 说明
        • Babel其实是几个模块化的包,其核心功能位于称为babel-core的npm包中,webpack可以把其不同的包整合在一起使用,对于每一个你需要的功能或拓展,你都需要安装单独的包(用得最多的是解析ES6的babel-preset-env包和解析JSX的babel-preset-react包)

      • 操作:
        • + -支持ES6及JSX
          • 1:  执行安装命令
            • cnpm i babel-core babel-loader babel-preset-env babel-preset-react -D
              • // babel-preset-env的env表示是对当前环境的预处理,而不是像以前使用babel-preset-es2015只能针对某个环境
          • 2:修改webpack.config.js,添加rule
            • ,             {                             // jsx配置                 test: /(\.jsx|\.js)$/,                    use: {                    // 注意use选择如果有多项配置,可写成这种对象形式                     loader: "babel-loader",                     options: {                         presets: [                             "env", "react"                         ]                     }                 },                 exclude: /node_modules/             }

              • 添加后的文件内容
                • // webpack.config.js const path = require('path'); module.exports = {     entry: path.join(__dirname, "/src/index.js"), // 入口文件     output: {         path: path.join( __dirname, "/dist"), //打包后的文件存放的地方         filename: "bundle.js" //打包后输出文件的文件名     },     devServer: {         contentBase: "./dist", // 本地服务器所加载文件的目录         port: "8088",  // 设置端口号为8088         inline: true, // 文件修改后实时刷新         historyApiFallback: true, //不跳转     },     devtool: 'source-map',  // 会生成对于调试的完整的.map文件,但同时也会减慢打包速度     module: {         rules: [             {                 test: /\.css$/,   // 正则匹配以.css结尾的文件                 use: ['style-loader', 'css-loader']  // 需要用的loader,一定是这个顺序,因为调用loader是从右往左编译的             },             {                 test: /\.(scss|sass)$/,   // 正则匹配以.scss和.sass结尾的文件                 use: ['style-loader', 'css-loader', 'sass-loader']  // 需要用的loader,一定是这个顺序,因为调用loader是从右往左编译的             },             {                             // jsx配置                 test: /(\.jsx|\.js)$/,                    use: {                    // 注意use选择如果有多项配置,可写成这种对象形式                     loader: "babel-loader",                     options: {                         presets: [                             "env", "react"                         ]                     }                 },                 exclude: /node_modules/             }         ]     } }

        • 支持react(使用react还得先安装两个模块react和react-dom)
          • 1: cnpm i react react-dom -D
          • 2:修改hello.js
            • // hello.js import React, {Component} from 'react'; // 这两个模块必须引入  let name = 'Alan';  export default class Hello extends Component{     render() {         return (             

                              {name}             
                      );     } }

          • 3: 修改index.js
            • //index.js  import './css/style.css';  // 导入css import './css/blue.scss';  // 导入scss  import React from 'react'; import {render} from 'react-dom'; import Hello from './hello'; // 可省略.js后缀名  render(, document.getElementById('root'));

          • 4:执行 npm run dev
          • 若此时执行的话可能会报错:
            • 报错的话两种解决方案
              • 1:回退版本
                • cnpm i babel-loader@7 babel-core babel-preset-env -D
              • 2:更新到最高版本
                • cnpm i babel-loader @babel/core @babel/preset-env webpack -D
    • 优化babel配置
      • 说明
        • 虽然babel完全可以在webpack.config.js中进行配置,但现在不是都提倡模块化嘛,也许之后babel膨胀了,增加了更多的配置项呢? 那我们不如把它提取出来,把它放到根目录下的.babelrc文件下(webpack会自动调用.babelrc里的babel配置选项)。

      • 操作:
        • 1:从webpack.config.js中去掉options的配置,
          •                    options: {                         presets: [                             "env", "react"                         ]                     }

            • 去掉后文件
              • // webpack.config.js const path = require('path'); module.exports = {     entry: path.join(__dirname, "/src/index.js"), // 入口文件     output: {         path: path.join( __dirname, "/dist"), //打包后的文件存放的地方         filename: "bundle.js" //打包后输出文件的文件名     },     devServer: {         contentBase: "./dist", // 本地服务器所加载文件的目录         port: "8088",  // 设置端口号为8088         inline: true, // 文件修改后实时刷新         historyApiFallback: true, //不跳转     },     devtool: 'source-map',  // 会生成对于调试的完整的.map文件,但同时也会减慢打包速度     module: {         rules: [             {                 test: /\.css$/,   // 正则匹配以.css结尾的文件                 use: ['style-loader', 'css-loader']  // 需要用的loader,一定是这个顺序,因为调用loader是从右往左编译的             },             {                 test: /\.(scss|sass)$/,   // 正则匹配以.scss和.sass结尾的文件                 use: ['style-loader', 'css-loader', 'sass-loader']  // 需要用的loader,一定是这个顺序,因为调用loader是从右往左编译的             },             {                             // jsx配置                 test: /(\.jsx|\.js)$/,                    use: {                    // 注意use选择如果有多项配置,可写成这种对象形式                     loader: "babel-loader"                 },                 exclude: /node_modules/   // 排除匹配node_modules模块             }         ]     } }

        • 2: 新建文件.babelrc
          • {     "presets": ["env", "react"] }
        • 3:执行 npm run dev
  • 插件(Plugins)
    • 说明
      • 插件(Plugins)是用来拓展Webpack功能的,它们会在整个构建过程中生效,执行相关的任务。 Loaders和Plugins常常被弄混,但是他们其实是完全不同的东西,可以这么来说,loaders是在打包构建过程中用来处理源文件的(JSX,Scss,Less..),一次处理一个,插件并不直接操作单个文件,它直接对整个构建过程其作用。

    • 插件如何使用
      • 操作:
        • 1: 通过npm安装插件
        • 2: 在webpack.config.js配置文件的plugins
        • 3: 运行npm run build 构建
    • 版权声明插件(HtmlWebpackPlugin)
      • 操作
        • 1:const webpack = require('webpack');
          • 这个插件不需要安装,是基于webpack的,需要引入webpack模块
        • 2:引入plugin
          •  plugins: [         new webpack.BannerPlugin('版权所有,翻版必究')  // new一个插件的实例      ]
        • 3:  npm run build
    • 自动生成html文件插件(HtmlWebpackPlugin)
      • 到目前为止我们都是使用一开始建好的index.html文件,而且也是手动引入bundle.js,要是以后我们引入不止一个js文件,而且更改js文件名的话,也得手动更改index.html中的js文件名,所以能不能自动生成index.html且自动引用打包后的js呢?HtmlWebpackPlugin插件就是用来解决这个问题的

        操作:

        • 1:安装插件

          • cnpm i html-webpack-plugin -D

          2:把dist整个文件夹删除

          3:src文件夹下新建一个index.template.html(名称自定义)文件模板(当然这个是可选的,因为就算不设置模板,HtmlWebpackPlugin插件也会生成默认html文件,这里我们设置模块会让我们的开发更加灵活)

          •             Here is Template           

                
              

          4:webpack.config.js中引入插件

          • const HtmlWebpackPlugin = require('html-webpack-plugin'); // 引入HtmlWebpackPlugin插件

            new HtmlWebpackPlugin({             template: path.join(__dirname, "/src/index.template.html")// new一个这个插件的实例,并传入相关的参数         })

            • plugins中使用插件

          然后我们使用npm run build进行打包

    • 清理./dist文件插件(CleanWebpackPlugin)
      • 你可能已经注意到,在我们删掉/dist文件夹之前,由于前面的代码示例遗留,导致我们的/dist文件夹比较杂乱。webpack会生成文件,然后将这些文件放置在/dist文件夹中,但是webpack无法追踪到哪些文件是实际在项目中用到的。  通常,在每次构建前清理/dist文件夹,是比较推荐的做法,因此只会生成用到的文件,这时候就用到CleanWebpackPlugin插件了。

        操作:

        • 1:安装插件
          • cnpm i clean-webpack-plugin -D
        • 2:webpack.config.js中引入插件
          • const CleanWebpackPlugin = require('clean-webpack-plugin'); // 引入CleanWebpackPlugin插件
            • const { CleanWebpackPlugin } = require('clean-webpack-plugin');
          • ,         new CleanWebpackPlugin(['dist']),  // 所要清理的文件夹名称
            • new CleanWebpackPlugin()
        • 3:运行:npm run build
    • 热更新插件(HotModuleReplacementPlugin)
      • HotModuleReplacementPlugin(HMR)是一个很实用的插件,可以在我们修改代码后自动刷新预览效果。
      • 操作:
        • 1:devServer配置项中添加hot: true参数
        • 2:因为HotModuleReplacementPlugin是webpack模块自带的,所以引入webpack后,在plugins配置项中直接使用即可。
        • 运行npm run build
  • 项目优化及拓展
    • 1:代码分离
      • 说明:
        • 在当前的开发环境都是提倡模块化,webpack自然不例外,我们前面的webpack.config.js配置文件,其实也没配置多少东西就这么多了,要是以后增加了更多配置,岂不是看得眼花缭乱,所以最好的方法就是把它拆分,方便管理

      • 操作
        • 1:我们在根目录下新建三个文件,分别为webpack.common.js、webpack.dev.js、webpack.prod.js,分别代表公共配置文件、开发环境配置文件、生产环境(指项目上线时的环境)配置文件。

          2:安装插件

          • cnpm i webpack-merge -D

          3:webpack.config.js文件拆分到新建的三个文件中

          • webpack.common.js
            • // webpack.common.js
              const path = require('path');  // 路径处理模块
              const webpack = require('webpack');  // 这个插件不需要安装,是基于webpack的,需要引入webpack模块
              const HtmlWebpackPlugin = require('html-webpack-plugin'); // 引入HtmlWebpackPlugin插件

              module.exports = {
                 entry: path.join(__dirname, "/src/index.js"), // 入口文件
                 output: {
                     path: path.join( __dirname, "/dist"), //打包后的文件存放的地方
                     filename: "bundle.js" //打包后输出文件的文件名
                 },
                 module: {
                     rules: [
                         {
                             test: /\.css$/,   // 正则匹配以.css结尾的文件
                             use: ['style-loader', 'css-loader']  // 需要用的loader,一定是这个顺序,因为调用loader是从右往左编译的
                         },
                         {
                             test: /\.(scss|sass)$/,   // 正则匹配以.scss和.sass结尾的文件
                             use: ['style-loader', 'css-loader', 'sass-loader']  // 需要用的loader,一定是这个顺序,因为调用loader是从右往左编译的
                         },
                         {                             // jsx配置
                             test: /(\.jsx|\.js)$/,   
                             use: {                    // 注意use选择如果有多项配置,可写成这种对象形式
                                 loader: "babel-loader"
                             },
                             exclude: /node_modules/   // 排除匹配node_modules模块
                         }
                     ]
                 },
                 plugins: [
                     new webpack.BannerPlugin('版权所有,翻版必究'),  // new一个插件的实例
                     new HtmlWebpackPlugin({
                         template: path.join(__dirname, "/src/index.template.html")// new一个这个插件的实例,并传入相关的参数
                     }),
                     new webpack.HotModuleReplacementPlugin()
                 ]
              }

          • webpack.dev.js
            • // webpack.dev.js
              // webpack.dev.js
              const merge = require('webpack-merge');  // 引入webpack-merge功能模块
              const common = require('./webpack.common.js'); // 引入webpack.common.js

              module.exports = merge(common, {   // 将webpack.common.js合并到当前文件
                 devServer: {
                     contentBase: "./dist",   // 本地服务器所加载文件的目录
                     port: "8088",  // 设置端口号为8088
                     inline: true,  // 文件修改后实时刷新
                     historyApiFallback: true, //不跳转
                     hot: true     //热加载
                 }
              })

          • webpack.prod.js
            • // webpack.prod.js
              const merge = require('webpack-merge');
              const common = require('./webpack.common.js');
              const {CleanWebpackPlugin} = require('clean-webpack-plugin')

              module.exports = merge(common, { // 将webpack.common.js合并到当前文件
                 devtool: 'source-map',  // 会生成对于调试的完整的.map文件,但同时也会减慢打包速度
                 plugins: [
                     new CleanWebpackPlugin()  // 所要清理的文件夹名称
                 ]
              })

          修改package.json文件中的script

          • "scripts": {     "build": "webpack --config webpack.prod.js",     "dev": "webpack-dev-server --open --config webpack.dev.js"   },

          运行npm run build

    • 2:多入口多出口
      • 到目前为止我们都是一个入口文件和一个出口文件,要是我不止一个入口文件呢
      • 操作:
        • 修改webpack.common.js,引入多入口和多出口
          •    entry: {         index: path.join(__dirname, "/src/index.js"),         two: path.join(__dirname, "/src/two.js")     },      output: {         path: path.join( __dirname, "/dist"), //打包后的文件存放的地方         filename: "[name].js" //打包后输出文件的文件名     },

        • 新建two.js
          • function two() {     let element = document.createElement('div');     element.innerHTML = '我是第二个入口文件';     return element; }  document.getElementById('root').appendChild(two());

        • 运行npm run build
    • 3:增加css前缀、分离css、消除冗余css、分离图片
  • 概念说明
    • 概念
      • 本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。

        从 webpack v4.0.0 开始,可以不用引入一个配置文件。然而,webpack 仍然还是高度可配置的。在开始前你需要先理解四个核心概念:

        • 入口(entry)
          • 入口起点(entry point)指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。  每个依赖项随即被处理,最后输出到称之为 bundles 的文件中

            可以通过在 webpack 配置中配置 entry 属性,来指定一个入口起点(或多个入口起点)。默认值为 ./src

            简单例子

            • webpack.config.js
              • module.exports = {   entry: './path/to/my/entry/file.js' };
        • 输出(output)
          • output 属性告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件,默认值为 ./dist。基本上,整个应用程序结构,都会被编译到你指定的输出路径的文件夹中。你可以通过在配置中指定一个 output 字段,来配置这些处理过程

            简单例子

            • webpack.config.js
              • 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'   } };

                我们通过 output.filename 和 output.path 属性,来告诉 webpack bundle 的名称,以及我们想要 bundle 生成(emit)到哪里。可能你想要了解在代码最上面导入的 path 模块是什么,它是一个 Node.js 核心模块,用于操作文件路径。

            特殊术语

            • 你可能会发现术语生成(emitted 或 emit)贯穿了我们整个文档和插件 API。它是“生产(produced)”或“释放(discharged)”的特殊术语。
        • loader
          • loader 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后你就可以利用 webpack 的打包能力,对它们进行处理。

            • 本质上,webpack loader 将所有类型的文件,转换为应用程序的依赖图(和最终的 bundle)可以直接引用的模块。

            注意,loader 能够 import 导入任何类型的模块(例如 .css 文件),这是 webpack 特有的功能,其他打包程序或任务执行器的可能并不支持。我们认为这种语言扩展是有很必要的,因为这可以使开发人员创建出更准确的依赖关系图。

            在更高层面,在 webpack 的配置中 loader 有两个目标

            • 1:test 属性,用于标识出应该被对应的 loader 进行转换的某个或某些文件。
            • 2:use 属性,表示进行转换时,应该使用哪个 loader。

            简单例子

            • webpack.config.js

              • const path = require('path');  const config = {   output: {     filename: 'my-first-webpack.bundle.js'   },   module: {     rules: [       { test: /\.txt$/, use: 'raw-loader' }     ]   } };  module.exports = config;

              以上配置中,对一个单独的 module 对象定义了 rules 属性,里面包含两个必须属性:test 和 use。这告诉 webpack 编译器(compiler) 如下信息

              重要的是要记得,在 webpack 配置中定义 loader 时,要定义在 module.rules 中,而不是 rules。然而,在定义错误时 webpack 会给出严重的警告。为了使你受益于此,如果没有按照正确方式去做,webpack 会“给出严重的警告”

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

            想要使用一个插件,你只需要 require() 它,然后把它添加到 plugins 数组中。多数插件可以通过选项(option)自定义。你也可以在一个配置文件中因为不同目的而多次使用同一个插件,这时需要通过使用 new 操作符来创建它的一个实例。

            简单例子

            • const HtmlWebpackPlugin = require('html-webpack-plugin'); // 通过 npm 安装 const webpack = require('webpack'); // 用于访问内置插件  const config = {   module: {     rules: [       { test: /\.txt$/, use: 'raw-loader' }     ]   },   plugins: [     new HtmlWebpackPlugin({template: './src/index.html'})   ] };  module.exports = config;

        • 模式
          • 通过选择 development 或 production 之中的一个,来设置 mode 参数,你可以启用相应模式下的 webpack 内置的优化
          • 简单例子
            • module.exports = {   mode: 'production' };
    • 入口起点(entry points)
      • 正如我们在起步中提到的,在 webpack 配置中有多种方式定义 entry 属性。除了解释为什么它可能非常有用,我们还将向你展示如何去配置 entry 属性。
      • 单个入口(简写)语法
        • 用法
          • entry: string|Array
        • webpack.config.js简写
          • const config = {   entry: './path/to/my/entry/file.js' };  module.exports = config;
        • entry正常写法
          • const config = {   entry: {     main: './path/to/my/entry/file.js'   } };
      • 对象语法
        • 用法
          • entry: {[entryChunkName: string]: string|Array}  webpack.config.js
        • webpack.config.js写法
          • const config = {   entry: {     app: './src/app.js',     vendors: './src/vendors.js'   } };
        • 对象语法会比较繁琐。然而,这是应用程序中定义入口的最可扩展的方式
          • “可扩展的 webpack 配置”是指,可重用并且可以与其他配置组合使用。这是一种流行的技术,用于将关注点(concern)从环境(environment)、构建目标(build target)、运行时(runtime)中分离。然后使用专门的工具(如 webpack-merge)将它们合并。

      • 常见场景
        • 分离 应用程序(app) 和 第三方库(vendor) 入口
          • const config = {   entry: {     app: './src/app.js',     vendors: './src/vendors.js'   } };
          • 这是什么?
            • 从表面上看,这告诉我们 webpack 从 app.js 和 vendors.js 开始创建依赖图(dependency graph)。这些依赖图是彼此完全分离、互相独立的(每个 bundle 中都有一个 webpack 引导(bootstrap))。这种方式比较常见于,只有一个入口起点(不包括 vendor)的单页应用程序(single page application)中。

          • 为什么?
            •  
              • 此设置允许你使用 CommonsChunkPlugin 从「应用程序 bundle」中提取 vendor 引用(vendor reference) 到 vendor bundle,并把引用 vendor 的部分替换为 __webpack_require__() 调用。如果应用程序 bundle 中没有 vendor 代码,那么你可以在 webpack 中实现被称为长效缓存的通用模式。

        • 多页面应用程序
          • webpack.config.js

            • const config = {   entry: {     pageOne: './src/pageOne/index.js',     pageTwo: './src/pageTwo/index.js',     pageThree: './src/pageThree/index.js'   } };

            我们告诉 webpack 需要 3 个独立分离的依赖图(如上面的示例)

            在多页应用中,(译注:每当页面跳转时)服务器将为你获取一个新的 HTML 文档。页面重新加载新文档,并且资源被重新下载。然而,这给了我们特殊的机会去做很多事:  使用 CommonsChunkPlugin 为每个页面间的应用程序共享代码创建 bundle。由于入口起点增多,多页应用能够复用入口起点之间的大量代码/模块,从而可以极大地从这些技术中受益。

    • 输出(output)
      • 配置 output 选项可以控制 webpack 如何向硬盘写入编译文件。注意,即使可以存在多个入口起点,但只指定一个输出配置。
      • 用法(Usage)
        • 在 webpack 中配置 output 属性的最低要求是,将它的值设置为一个对象,包括以下两点
          • filename 用于输出文件的文件名。
          • 目标输出目录 path 的绝对路径。
        • webpack.config.js
          • const config = {   output: {     filename: 'bundle.js',     path: '/home/proj/public/assets'   } };  module.exports = config;

            此配置将一个单独的 bundle.js 文件输出到 /home/proj/public/assets 目录中。

      • 多个入口起点
        • 如果配置创建了多个单独的 "chunk"(例如,使用多个入口起点或使用像 CommonsChunkPlugin 这样的插件),则应该使用占位符(substitutions)来确保每个文件具有唯一的名称。

          示例

          • {   entry: {     app: './src/app.js',     search: './src/search.js'   },   output: {     filename: '[name].js',     path: __dirname + '/dist'   } }  // 写入到硬盘:./dist/app.js, ./dist/search.js

      • 高级进阶
        • 使用 CDN 和资源 hash 的复杂示例
        • config.js
          • output: {   path: "/home/proj/cdn/assets/[hash]",   publicPath: "http://cdn.example.com/assets/[hash]/" }

            在编译时不知道最终输出文件的 publicPath 的情况下,publicPath 可以留空,并且在入口起点文件运行时动态设置。如果你在编译时不知道 publicPath,你可以先忽略它,并且在入口起点设置 __webpack_public_path__。

    • 模式(mode)
      • 提供 mode 配置选项,告知 webpack 使用相应模式的内置优化。
      • 用法
        • 1:只在配置中提供 mode 选项:
          • module.exports = {   mode: 'production' };
        • 2:或者从 CLI 参数中传递:
          • webpack --mode=production
      • 支持的字符串
        • development
          • 会将 process.env.NODE_ENV 的值设为 development。启用 NamedChunksPlugin 和 NamedModulesPlugin。
            • // webpack.development.config.js module.exports = { + mode: 'development' - plugins: [ -   new webpack.NamedModulesPlugin(), -   new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("development") }), - ] }

        • production
          • 会将 process.env.NODE_ENV 的值设为 production。启用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin 和 UglifyJsPlugin.

            • // webpack.production.config.js module.exports = { +  mode: 'production', -  plugins: [ -    new UglifyJsPlugin(/* ... */), -    new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("production") }), -    new webpack.optimize.ModuleConcatenationPlugin(), -    new webpack.NoEmitOnErrorsPlugin() -  ] }

    • loader
      • oader 用于对模块的源代码进行转换。loader 可以使你在 import 或"加载"模块时预处理文件。因此,loader 类似于其他构建工具中“任务(task)”,并提供了处理前端构建步骤的强大方法。loader 可以将文件从不同的语言(如 TypeScript)转换为 JavaScript,或将内联图像转换为 data URL。loader 甚至允许你直接在 JavaScript 模块中 import CSS文件!

        示例

        • 首先安装相对应的 loader:
          • npm install --save-dev css-loader npm install --save-dev ts-loader
        • webpack.config.js
          • module.exports = {   module: {     rules: [       { test: /\.css$/, use: 'css-loader' },       { test: /\.ts$/, use: 'ts-loader' }     ]   } };

        使用 loader三种方式

        • 配置(推荐):在 webpack.config.js 文件中指定 loader
          • module.rules 允许你在 webpack 配置中指定多个 loader。 这是展示 loader 的一种简明方式,并且有助于使代码变得简洁。同时让你对各个 loader 有个全局概览:
          • 用法
            •  module: {     rules: [       {         test: /\.css$/,         use: [           { loader: 'style-loader' },           {             loader: 'css-loader',             options: {               modules: true             }           }         ]       }     ]   }

        • 内联:在每个 import 语句中显式指定 loader。
          • 可以在 import 语句或任何等效于 "import" 的方式中指定 loader。使用 ! 将资源中的 loader 分开。分开的每个部分都相对于当前目录解析。
          • 用法
            • import Styles from 'style-loader!css-loader?modules!./styles.css';
          • 说明:
            • 通过前置所有规则及使用 !,可以对应覆盖到配置中的任意 loader。
            • 选项可以传递查询参数,例如 ?key=value&foo=bar,或者一个 JSON 对象,例如 ?{"key":"value","foo":"bar"}。
        • CLI:在 shell 命令中指定它们
          • 也可以通过 CLI 使用 loader:
          • webpack --module-bind jade-loader --module-bind 'css=style-loader!css-loader'

        loader 特性

        • loader 支持链式传递。能够对资源使用流水线(pipeline)。一组链式的 loader 将按照相反的顺序执行。loader 链中的第一个 loader 返回值给下一个 loader。在最后一个 loader,返回 webpack 所预期的 JavaScript。

          loader 可以是同步的,也可以是异步的。

          loader 运行在 Node.js 中,并且能够执行任何可能的操作。

          loader 接收查询参数。用于对 loader 传递配置。

          oader 也能够使用 options 对象进行配置。

          除了使用 package.json 常见的 main 属性,还可以将普通的 npm 模块导出为 loader,做法是在 package.json 里定义一个 loader 字段。

          插件(plugin)可以为 loader 带来更多特性

          loader 能够产生额外的任意文件。

        解析 loader

        • loader 遵循标准的模块解析。多数情况下,loader 将从模块路径(通常将模块路径认为是 npm install, node_modules)解析。

          loader 模块需要导出为一个函数,并且使用 Node.js 兼容的 JavaScript 编写。通常使用 npm 进行管理,但是也可以将自定义 loader 作为应用程序中的文件。按照约定,loader 通常被命名为 xxx-loader(例如 json-loader)。有关详细信息,请查看 如何编写 loader?

    • 插件(plugins)
      • 插件是 webpack 的支柱功能。webpack 自身也是构建于,你在 webpack 配置中用到的相同的插件系统之上!  插件目的在于解决 loader 无法实现的其他事。
      • 剖析
        • webpack 插件是一个具有 apply 属性的 JavaScript 对象。apply 属性会被 webpack compiler 调用,并且 compiler 对象可在整个编译生命周期访问。
        • ConsoleLogOnBuildWebpackPlugin.js
          • const pluginName = 'ConsoleLogOnBuildWebpackPlugin';  class ConsoleLogOnBuildWebpackPlugin {     apply(compiler) {         compiler.hooks.run.tap(pluginName, compilation => {             console.log("webpack 构建过程开始!");         });     } }

            • compiler hook 的 tap 方法的第一个参数,应该是驼峰式命名的插件名称。建议为此使用一个常量,以便它可以在所有 hook 中复用。
      • 用法
        • 由于插件可以携带参数/选项,你必须在 webpack 配置中,向 plugins 属性传入 new 实例。
        • 根据你的 webpack 用法,这里有多种方式使用插件
      • 配置
        • webpack.config.js
          • const HtmlWebpackPlugin = require('html-webpack-plugin'); //通过 npm 安装 const webpack = require('webpack'); //访问内置的插件 const path = require('path');  const config = {   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 webpack.optimize.UglifyJsPlugin(),     new HtmlWebpackPlugin({template: './src/index.html'})   ] };  module.exports = config;

    • 配置(configuration)
      • 你可能已经注意到,很少有 webpack 配置看起来很完全相同。这是因为 webpack 的配置文件,是导出一个对象的 JavaScript 文件。此对象,由 webpack 根据对象定义的属性进行解析

        基本配置

        • var path = require('path');  module.exports = {   mode: 'development',   entry: './foo.js',   output: {     path: path.resolve(__dirname, 'dist'),     filename: 'foo.bundle.js'   } };

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

          每个模块具有比完整程序更小的接触面,使得校验、调试、测试轻而易举。 精心编写的模块提供了可靠的抽象和封装界限,使得应用程序中每个模块都具有条理清楚的设计和明确的目的。

          Node.js 从最一开始就支持模块化编程。然而,在 web,模块化的支持正缓慢到来。在 web 存在多种支持 JavaScript 模块化的工具,这些工具各有优势和限制。webpack 基于从这些系统获得的经验教训,并将模块的概念应用于项目中的任何文件。

      • 什么是 webpack 模块
        • 对比 Node.js 模块,webpack 模块能够以各种方式表达它们的依赖关系,几个例子
          • ES2015 import 语句
          • CommonJS require() 语句
          • AMD define 和 require 语句
          • css/sass/less 文件中的 @import 语句。
          • 样式(url(...))或 HTML 文件()中的图片链接(image url
      • 支持的模块类型
        • webpack 通过 loader 可以支持各种语言和预处理器编写模块。loader 描述了 webpack 如何处理 非 JavaScript(non-JavaScript) _模块_,并且在 bundle 中引入这些依赖。 webpack 社区已经为各种流行语言和语言处理器构建了 loader,包括

          • CoffeeScript
          • TypeScript
          • ESNext (Babel)
          • Sass
          • Less
          • Stylus
    • 模块解析(module resolution)
      • 模块解析
        • resolver 是一个库(library),用于帮助找到模块的绝对路径。一个模块可以作为另一个模块的依赖模块,然后被后者引用

          • import foo from 'path/to/module'
          • require('path/to/module')

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

      • webpack 中的解析规则
        • 使用 enhanced-resolve,webpack 能够解析三种文件路径
          • 绝对路径
            • import "/home/me/file";  import "C:\\Users\\me\\file";
            • 由于我们已经取得文件的绝对路径,因此不需要进一步再做解析
          • 相对路径
            • import "../src/file1"; import "./file2";

              在这种情况下,使用 import 或 require 的资源文件(resource file)所在的目录被认为是上下文目录(context directory)。在 import/require 中给定的相对路径,会添加此上下文路径(context path),以产生模块的绝对路径(absolute path)。

          • 模块路径
            • import "module"; import "module/lib/file";
            • 模块将在 resolve.modules 中指定的所有目录内搜索。 你可以替换初始模块路径,此替换路径通过使用 resolve.alias 配置选项来创建一个别名。
      • 解析 Loader(Resolving Loaders)
        • Loader 解析遵循与文件解析器指定的规则相同的规则。但是 resolveLoader 配置选项可以用来为 Loader 提供独立的解析规则。
      • 缓存
        • 每个文件系统访问都被缓存,以便更快触发对同一文件的多个并行或串行请求。在观察模式下,只有修改过的文件会从缓存中摘出。如果关闭观察模式,在每次编译前清理缓存。
    • 依赖图(dependency graph)
      • 任何时候,一个文件依赖于另一个文件,webpack 就把此视为文件之间有 依赖关系 。这使得 webpack 可以接收非代码资源(non-code asset)(例如图像或 web 字体),并且可以把它们作为 _依赖_ 提供给你的应用程序。

        webpack 从命令行或配置文件中定义的一个模块列表开始,处理你的应用程序。 从这些 入口起点 开始,webpack 递归地构建一个 依赖图 ,这个依赖图包含着应用程序所需的每个模块,然后将所有这些模块打包为少量的 bundle - 通常只有一个 - 可由浏览器加载。

    • manifest
      • 在使用 webpack 构建的典型应用程序或站点中,有三种主要的代码类型
        • 1:你或你的团队编写的源码。
        • 2:你的源码会依赖的任何第三方的 library 或 "vendor" 代码
        • 3:webpack 的 runtime 和 manifest,管理所有模块的交互。
      • Runtime
        • 如上所述,我们这里只简略地介绍一下。runtime,以及伴随的 manifest 数据,主要是指:在浏览器运行时,webpack 用来连接模块化的应用程序的所有代码。runtime 包含:在模块交互时,连接模块所需的加载和解析逻辑。包括浏览器中的已加载模块的连接,以及懒加载模块的执行逻辑。

      • Manifest
        • 那么,一旦你的应用程序中,形如 index.html 文件、一些 bundle 和各种资源加载到浏览器中,会发生什么?你精心安排的 /src 目录的文件结构现在已经不存在,所以 webpack 如何管理所有模块之间的交互呢?这就是 manifest 数据用途的由来……  当编译器(compiler)开始执行、解析和映射应用程序时,它会保留所有模块的详细要点。这个数据集合称为 "Manifest",当完成打包并发送到浏览器时,会在运行时通过 Manifest 来解析和加载模块。无论你选择哪种模块语法,那些 import 或 require 语句现在都已经转换为 __webpack_require__ 方法,此方法指向模块标识符(module identifier)。通过使用 manifest 中的数据,runtime 将能够查询模块标识符,检索出背后对应的模块。

    • 构建目标(targets)
      • 因为服务器和浏览器代码都可以用 JavaScript 编写,所以 webpack 提供了多种构建目标(target),你可以在你的 webpack 配置中设置。
      • 用法
        • 要设置 target 属性,只需要在你的 webpack 配置中设置 target 的值
          • module.exports = {   target: 'node' };
        • 在上面例子中,使用 node webpack 会编译为用于「类 Node.js」环境(使用 Node.js 的 require ,而不是使用任意内置模块(如 fs 或 path)来加载 chunk)。
      • 多个 Target
        • 管 webpack 不支持向 target 传入多个字符串,你可以通过打包两份分离的配置来创建同构的库
        • webpack.config.js
          • var path = require('path'); var serverConfig = {   target: 'node',   output: {     path: path.resolve(__dirname, 'dist'),     filename: 'lib.node.js'   }   //… };  var clientConfig = {   target: 'web', // <=== 默认是 'web',可省略   output: {     path: path.resolve(__dirname, 'dist'),     filename: 'lib.js'   }   //… };  module.exports = [ serverConfig, clientConfig ];

      • 资源
        • compare-webpack-target-bundles
          • 有关「测试和查看」不同的 webpack target 的大量资源。也有大量 bug 报告。
        • Boilerplate of Electron-React Application
          • 一个 electron 主进程和渲染进程构建过程的很好的例子。
    • 模块热替换(hot module replacement)
      • 模块热替换(HMR - Hot Module Replacement)功能会在应用程序运行过程中替换、添加或删除模块,而无需重新加载整个页面。主要是通过以下几种方式,来显著加快开发速度
        • 保留在完全重新加载页面时丢失的应用程序状态。
        • 只更新变更内容,以节省宝贵的开发时间。
        • 调整样式更加快速 - 几乎相当于在浏览器调试器中更改样式。
      • 这一切是如何运行的
        • 在应用程序中
          • 通过以下步骤,可以做到在应用程序中置换(swap in and out)模块
            • 应用程序代码要求 HMR runtime 检查更新
            • HMR runtime(异步)下载更新,然后通知应用程序代码。
            • 应用程序代码要求 HMR runtime 应用更新
            • HMR runtime(同步)应用更新。
        • 在编译器中
          • 除了普通资源,编译器(compiler)需要发出 "update",以允许更新之前的版本到新的版本。"update" 由两部分组成:
            • 更新后的 manifest(JSON)
            • 一个或多个更新后的 chunk (JavaScript)
        • 在模块中
          • HMR 是可选功能,只会影响包含 HMR 代码的模块。举个例子,通过 style-loader 为 style 样式追加补丁。为了运行追加补丁,style-loader 实现了 HMR 接口;当它通过 HMR 接收到更新,它会使用新的样式替换旧的样式。

            类似的,当在一个模块中实现了 HMR 接口,你可以描述出当模块被更新后发生了什么。然而在多数情况下,不需要强制在每个模块中写入 HMR 代码。如果一个模块没有 HMR 处理函数,更新就会冒泡(bubble up)。这意味着一个简单的处理函数能够对整个模块树(complete module tree)进行更新。如果在这个模块树中,一个单独的模块被更新,那么整组依赖模块都会被重新加载。

        • 在 HMR Runtime 中
          • 对于模块系统的 runtime,附加的代码被发送到 parents 和 children 跟踪模块。在管理方面,runtime 支持两个方法 check 和 apply。

            check 发送 HTTP 请求来更新 manifest。如果请求失败,说明没有可用更新。如果请求成功,待更新 chunk 会和当前加载过的 chunk 进行比较。对每个加载过的 chunk,会下载相对应的待更新 chunk。当所有待更新 chunk 完成下载,就会准备切换到 ready 状态

            apply 方法将所有被更新模块标记为无效。对于每个无效模块,都需要在模块中有一个更新处理函数(update handler),或者在它的父级模块们中有更新处理函数。否则,无效标记冒泡,并也使父级无效。每个冒泡继续,直到到达应用程序入口起点,或者到达带有更新处理函数的模块(以最先到达为准,冒泡停止)。如果它从入口起点开始冒泡,则此过程失败。

            之后,所有无效模块都被(通过 dispose 处理函数)处理和解除加载。然后更新当前 hash,并且调用所有 "accept" 处理函数。runtime 切换回闲置状态(idle state),一切照常继续。

  • 你可能感兴趣的:(webpack,webpack)