webpack模块打包 使用 构建

上一篇主要说了webpack处理ES Module 模块引入方式的打包
其实webpack也支持其他的规范打包如CommonJs,CMD,AMD...

npx 会自动查找当前依赖包中的可执行文件,如果找不到,就会去 PATH 里找。如果依然找不到,就会帮你安装。
上一篇说到的,npx webpack index.js 意思就是npx帮你找到可执行的webpack然后去打包index.js,当然,我们也可以通过简单的配置webpack.config.js通过配置script命令去执行你的打包命令,配置好后,只要执行npm run build就可以了,代码如下

webpack.config.js

const path = require('path');

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

package.json

{
  "name": "webpack-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "dependencies": {
    "webpack-cli": "^3.3.12",
    "webpack": "^4.44.0"
  },
  "devDependencies": {},
  "scripts": {
    "build": "webpack"
  },
  "author": "",
  "license": "ISC"
}

webpack-cli 是webpack提供的一个node工具链,从而是我们可以直接执行webpack npx 等,当你不写配置文件的时候,webpack 会执行他的内部打包默认配置。webpack.config.js是默认写法。假如你把配置文件写成config.js,那么你在执行打包命令的时候,就需要这么写webpack --config config.js告诉webpack他的打包配置文件是config.js

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/
你会发现打包的时候有这样的警告,大概意思是,让你配置mode(环境) development(代码没有被压缩方便调试) 或者 production(代码被压缩),接下来我们配置一下

const path = require('path');

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

执行webpack 打包的时候输出如下

Hash: 84bea18a4a1fab7a5786       本次打包的唯一hash
Version: webpack 4.44.0                  使用webpack的版本
Time: 73ms                                      本次打包总共耗时
Built at: 2020/07/28 下午4:24:30        编译完日期
    Asset      Size  Chunks             Chunk Names      输出的一下表格目录结构  编译出的文件、大小、文件的id、编译的文件名
bundle.js  6.08 KiB    main  [emitted]  main          
Entrypoint main = bundle.js
[./src/content.js] 179 bytes {main} [built]
[./src/footer.js] 176 bytes {main} [built]
[./src/header.js] 176 bytes {main} [built]
[./src/index.js] 135 bytes {main} [built]

webpack 默认只认识js 文件,当引入其他文件的时候该怎么打包呢,举个例子,打包图片,首先安装npm install file-loader -D然后配置module就可以打包图片了,配置如下

const path = require('path');

module.exports = {
  mode: 'development',
  entry: {
    main: './src/index.js'
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif)$/i,    // i 正则,代表结尾, 以.png .jpg等结尾的文件,同样可以配置 txt excel 等所有文件 
        use: 'file-loader',
          options: {
            name: '[name]_[hash].[ext]',        // 图片重命名
            outputPath: 'images/'                  // 输入文件目录
          }
      }
    ]
  }
}

url-loader也可以实现图片打包,可以替代file-loader

const path = require('path');

module.exports = {
  mode: 'development',
  entry: {
    main: './src/index.js'
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif)$/i,
        use: {
          loader: 'url-loader',
          options: {
            name: '[name]_[hash].[ext]',
            outputPath: 'images/',
            limit: 2048    //  `url-loader` 功能类似于 [`file-loader`](https://github.com/webpack-contrib/file-loader),但是在文件大小(单位 byte)低于指定的限制时,可以返回一个 DataURL,小于转成base64打包到js里反之输出文件
          }
        }
      }
    ]
  }
}

打包css样式

const path = require('path');

module.exports = {
  mode: 'development',
  entry: {
    main: './src/index.js'
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif)$/i,
        use: {
          loader: 'url-loader',
          options: {
            name: '[name]_[hash].[ext]',
            outputPath: 'images/',
            limit: 2048
          }
        }
      },
      {
        test: /\.css$/i,
        use: [ 'style-loader', 'css-loader' ]   // css-loader会分析用到了几个css把所有的css合并成一个css, 'style-loader',会把合并好的css挂载到head标签里,执行顺序,从下到上,右到左
      }
    ]
  }
}

scss的使用npm install sass-loader node-sass webpack --save-dev

const path = require('path');

module.exports = {
  mode: 'development',
  entry: {
    main: './src/index.js'
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif)$/i,
        use: {
          loader: 'url-loader',
          options: {
            name: '[name]_[hash].[ext]',
            outputPath: 'images/',
            limit: 2048
          }
        }
      },
      {
        test: /\.s[ac]ss$/i,
        use: [ 'style-loader', 'css-loader', 'sass-loader' ]
      }
    ]
  }
}

做css浏览器兼容,这时候就得需要postcss-loader了npm i -D postcss-loader,npm i autoprefixer -Dpostcss-loader插件实现自动配置私有前缀,

const path = require('path');

module.exports = {
  mode: 'development',
  entry: {
    main: './src/index.js'
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif)$/i,
        use: {
          loader: 'url-loader',
          options: {
            name: '[name]_[hash].[ext]',
            outputPath: 'images/',
            limit: 2048
          }
        }
      },
      {
        test: /\.scss$/i,
        use: [
          'style-loader',
          { 
            loader: 'css-loader',
            options: {
              importLoaders: 2,
              modules: true   // 开启模块化,这样样式就用作用域,就可以在代码中怎么引入`import style from './index.scss';`
            } 
          },
          'sass-loader',
          'postcss-loader'
        ]
      }
    ]
  }
}

然后创建postcss.config.js引用loader插件,postcss.config.js查找规则由当前文件向外层文件查找,找到停止,所以可以在不同文件中配置不用的配置

module.exports = {
  plugins: [
    require('autoprefixer')
  ]
}

最后结合package.json中的browserslist 做样式兼容

{
  "name": "webpack-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "dependencies": {
    "webpack-cli": "^3.3.12"
  },
  "devDependencies": {
    "autoprefixer": "^9.8.5",
    "css-loader": "^4.0.0",
    "file-loader": "^6.0.0",
    "node-sass": "^4.14.1",
    "postcss-loader": "^3.0.0",
    "sass-loader": "^9.0.2",
    "style-loader": "^1.2.1",
    "url-loader": "^4.1.0",
    "webpack": "^4.44.0"
  },
  "scripts": {
    "build": "webpack"
  },
  "author": "",
  "license": "ISC",
  "browserslist": [
    "last 2 versions",
    "> 1%",
    "iOS 7",
    "last 3 iOS versions"
  ]
}

字体文件也可以通过file-loader配置

const path = require('path');

module.exports = {
  mode: 'development',
  entry: {
    main: './src/index.js'
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif)$/i,
        use: {
          loader: 'url-loader',
          options: {
            name: '[name]_[hash].[ext]',
            outputPath: 'images/',
            limit: 2048
          }
        }
      },
      {
        test: /\.scss$/i,
        use: [
          'style-loader',
          { 
            loader: 'css-loader',
            options: {
              importLoaders: 2,
              // modules: true
            } 
          },
          'sass-loader',
          'postcss-loader'
        ]
      },
      {
        test: /\.(eot|woff2|woff|ttf|svg)$/,
        use: [
          'file-loader',
        ]
      }
    ]
  }
}

plugin的简单使用,如:
html-webpack-plugin 会在打包结束后,自动生成一个html文件,并把打包生成的js自动引入到这个html中

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  mode: 'development',
  entry: {
    main: './src/index.js'
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [new HtmlWebpackPlugin()],
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif)$/i,
        use: {
          loader: 'url-loader',
          options: {
            name: '[name]_[hash].[ext]',
            outputPath: 'images/',
            limit: 2048
          }
        }
      },
      {
        test: /\.scss$/i,
        use: [
          'style-loader',
          { 
            loader: 'css-loader',
            options: {
              importLoaders: 2,
              // modules: true
            } 
          },
          'sass-loader',
          'postcss-loader'
        ]
      },
      {
        test: /\.(eot|woff2|woff|ttf|svg)$/,
        use: [
          'file-loader',
        ]
      }
    ]
  }
}

loader是什么,webpack不能识别非js结尾的文件,所以就需要loader告诉webpack怎么去打包编译,而plugin,更像是一个生命周期函数,他可以在webpack运行到某个时刻的时候,帮你做一些事情

clean-webpack-plugin使用

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
  mode: 'development',
  entry: {
    main: './src/index.js'
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: 'src/index.html'
    }),
    new CleanWebpackPlugin()    // 默认清理output.path中的文件
  ],
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif)$/i,
        use: {
          loader: 'url-loader',
          options: {
            name: '[name]_[hash].[ext]',
            outputPath: 'images/',
            limit: 2048
          }
        }
      },
      {
        test: /\.scss$/i,
        use: [
          'style-loader',
          { 
            loader: 'css-loader',
            options: {
              importLoaders: 2,
              // modules: true
            } 
          },
          'sass-loader',
          'postcss-loader'
        ]
      },
      {
        test: /\.(eot|woff2|woff|ttf|svg)$/,
        use: [
          'file-loader',
        ]
      }
    ]
  }
}

多个打包入口的配置,配置多个打包入口的时候输出文件的名字就不可以写死了,可以使用动态占位符,[name] [hash]等等,html-webpack-plugin也会自动帮你引入打包出来的所有js,同时你也可以给output配置参数,如publicPath输出文件的目录,域名等

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
  mode: 'development',
  entry: {
    main: './src/index.js',
    sub: './src/index.js'
  },
  output: {
    publicPath: 'http://www.cdn.cn',
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: 'src/index.html'
    }),
    new CleanWebpackPlugin()
  ],
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif)$/i,
        use: {
          loader: 'url-loader',
          options: {
            name: '[name]_[hash].[ext]',
            outputPath: 'images/',
            limit: 2048
          }
        }
      },
      {
        test: /\.scss$/i,
        use: [
          'style-loader',
          { 
            loader: 'css-loader',
            options: {
              importLoaders: 2,
              // modules: true
            } 
          },
          'sass-loader',
          'postcss-loader'
        ]
      },
      {
        test: /\.(eot|woff2|woff|ttf|svg)$/,
        use: [
          'file-loader',
        ]
      }
    ]
  }
}

sourceMap的配置
sourceMap,会在当你打包出来的文件报错的时候,帮你找到对应代码的映射关系,从而准确的定位到源代码里面的错误在哪里
开发环境推荐配置 devtool: 'cheap-module-eval-source-map' 这种打包速度快,同时错误提示比较全
生产环境推荐配置 devtool: 'cheap-module-source-map', 便于调试,当然你也可以配置成none 关闭映射。
cheap 只提示多好行出错了,不提示多少列。module除了业务代码,一些loader里面的错误也报一下。 source-map自动生成.map文件。inline映射关系打包在代码里 。eval把对应的代码和source-map一起执行,提高打包效率
参考文档

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
  mode: 'development',
  devtool: 'cheap-module-eval-source-map',    // 参打开代码映射,参数none 关闭
  entry: {
    main: './src/index.js',
    sub: './src/index.js'
  },
  output: {
    publicPath: './',
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: 'src/index.html'
    }),
    new CleanWebpackPlugin()
  ],
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif)$/i,
        use: {
          loader: 'url-loader',
          options: {
            name: '[name]_[hash].[ext]',
            outputPath: 'images/',
            limit: 2048
          }
        }
      },
      {
        test: /\.scss$/i,
        use: [
          'style-loader',
          { 
            loader: 'css-loader',
            options: {
              importLoaders: 2,
              // modules: true
            } 
          },
          'sass-loader',
          'postcss-loader'
        ]
      },
      {
        test: /\.(eot|woff2|woff|ttf|svg)$/,
        use: [
          'file-loader',
        ]
      }
    ]
  }
}

output.library和output.libraryTarget属性可能大家都会比较陌生,因为一般如果只在项目中使用 webpack 不需要关注这两个属性,但是如果是开发类库,那么这两个属性就是必须了解的。

umd

(function webpackUniversalModuleDefinition(..){..})(..)

amd

define(['demo'], function(demo) {
  demo();
});

commonjs

const demo = require('demo');
demo();

es module

import demo from 'demo';
demo();

大家思考一下,为什么这个类库能支持不同方式的引入?如何实现的?这就是 webpack 配置output.library和output.libraryTarget提供的功能。

热更新
webpack --watch 最简单的

webpack-dev-server

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
  mode: 'development',
  devtool: 'cheap-module-eval-source-map',    // 参打开代码映射,参数none 关闭
  entry: {
    main: './src/index.js',
    sub: './src/index.js'
  },
  devServer: {
    contentBase: './dist',
    open: true
  },
  plugins: [
    new CleanWebpackPlugin({ cleanStaleWebpackAssets: false }),
    new HtmlWebpackPlugin({
      title: 'Development',
      template: 'src/index.html'
    })
  ],
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif)$/i,
        use: {
          loader: 'url-loader',
          options: {
            name: '[name]_[hash].[ext]',
            outputPath: 'images/',
            limit: 2048
          }
        }
      },
      {
        test: /\.scss$/i,
        use: [
          'style-loader',
          { 
            loader: 'css-loader',
            options: {
              importLoaders: 2,
              // modules: true
            } 
          },
          'sass-loader',
          'postcss-loader'
        ]
      },
      {
        test: /\.(eot|woff2|woff|ttf|svg)$/,
        use: [
          'file-loader',
        ]
      }
    ]
  }
}
运行 "start": "webpack-dev-server ",

使用webpack-dev-middleware实现一个webpack-dev-server, 在node中使用webpack

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
  mode: 'development',
  devtool: 'cheap-module-eval-source-map',    // 参打开代码映射,参数none 关闭
  entry: {
    main: './src/index.js',
    sub: './src/index.js'
  },
  devServer: {
    contentBase: './dist',
    open: true
  },
  plugins: [
    new CleanWebpackPlugin({ cleanStaleWebpackAssets: false }),
    new HtmlWebpackPlugin({
      title: 'Development',
      template: 'src/index.html'
    })
  ],
  output: {
    publicPath: '/',        // 指向跟目录。对应server.js   一个node服务 
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif)$/i,
        use: {
          loader: 'url-loader',
          options: {
            name: '[name]_[hash].[ext]',
            outputPath: 'images/',
            limit: 2048
          }
        }
      },
      {
        test: /\.scss$/i,
        use: [
          'style-loader',
          { 
            loader: 'css-loader',
            options: {
              importLoaders: 2,
              // modules: true
            } 
          },
          'sass-loader',
          'postcss-loader'
        ]
      },
      {
        test: /\.(eot|woff2|woff|ttf|svg)$/,
        use: [
          'file-loader',
        ]
      }
    ]
  }
}

server.js

const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const config = require('./webpack.config');
const complier = webpack(config);  // 返回一个编译器

const app = express();
app.use(webpackDevMiddleware(complier, {
  publicPath: config.output.publicPath
}))

app.listen(3000, () => {
  console.log('server in running!!!');
})

运行 "middleware": "node server.js"

Hot Module Replacement 热模块替换HMR,比如我更改了页面的样式,webpack检测到代码变化,会给我们重新打包编译,导致,页面重新加载,然而这并不是我们想要的,我们就可以通过HMR进行热模块替换,dom不变只改变样式,不触发所有文件的重新打包。
当你使用了HMR的时候,样式改变不会重新加载整个html。只会替换css。当多个js模块引用,其中一个js模块发生改变的时候,这个这么写,从而只更新这一个模块。比如在写vue项目中会有这样类似的功能但是我们代码中并没有写类似module.hot.accept这样的代码,这是因为vue-loader已经替我们做了这一步

if(module.hot) {
  module.hot.accept('监听变化的模块地址', () => { 
    代码发生改变时所执行的事 
  })
}
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const webpack = require('webpack');

module.exports = {
  mode: 'development',
  devtool: 'cheap-module-eval-source-map',    // 参打开代码映射,参数none 关闭
  entry: {
    main: './src/index.js',
    sub: './src/index.js'
  },
  devServer: {
    contentBase: './dist',
    open: true,
    hot: true,
    hotOnly: true
  },
  plugins: [
    new CleanWebpackPlugin({ cleanStaleWebpackAssets: false }),
    new HtmlWebpackPlugin({
      title: 'Development',
      template: 'src/index.html'
    }),
    new webpack.HotModuleReplacementPlugin() 
  ],
  output: {
    publicPath: '/',        // 指向跟目录。对应server.js   一个node服务 
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif)$/i,
        use: {
          loader: 'url-loader',
          options: {
            name: '[name]_[hash].[ext]',
            outputPath: 'images/',
            limit: 2048
          }
        }
      },
      {
        test: /\.scss$/i,
        use: [
          'style-loader',
          { 
            loader: 'css-loader',
            options: {
              importLoaders: 2,
              // modules: true
            } 
          },
          'sass-loader',
          'postcss-loader'
        ]
      },
      {
        test: /\.(eot|woff2|woff|ttf|svg)$/,
        use: [
          'file-loader',
        ]
      }
    ]
  }
}

使用babel处理ES6+语法
npm install --save-dev babel-loader @babel/core babel-loade把babel和webpack建立连接,通信,@babel/core是babel的核心包
npm install @babel/preset-env --save-dev @babel/preset-env是转化为es5代码,里面包含了所有es6+转换成es5的翻译规则。
npm install --save @babel/polyfill 对低版本浏览器的语法兼容,全局引入import "@babel/polyfill";然后在webpack中配置按需加载
npm install --save-dev @babel/plugin-transform-runtime npm install --save @babel/runtime npm install --save @babel/runtime-corejs2

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const webpack = require('webpack');

module.exports = {
  mode: 'development',
  devtool: 'cheap-module-eval-source-map',    // 参打开代码映射,参数none 关闭
  entry: {
    main: './src/index.js',
    sub: './src/index.js'
  },
  devServer: {
    contentBase: './dist',
    open: true,
    hot: true,
    hotOnly: true
  },
  plugins: [
    new CleanWebpackPlugin({ cleanStaleWebpackAssets: false }),
    new HtmlWebpackPlugin({
      title: 'Development',
      template: 'src/index.html'
    }),
    new webpack.HotModuleReplacementPlugin() 
  ],
  output: {
    publicPath: '/',        // 指向跟目录。对应server.js   一个node服务 
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif)$/i,
        use: {
          loader: 'url-loader',
          options: {
            name: '[name]_[hash].[ext]',
            outputPath: 'images/',
            limit: 2048
          }
        }
      },
      {
        test: /\.scss$/i,
        use: [
          'style-loader',
          { 
            loader: 'css-loader',
            options: {
              importLoaders: 2,
              // modules: true
            } 
          },
          'sass-loader',
          'postcss-loader'
        ]
      },
      {
        test: /\.(eot|woff2|woff|ttf|svg)$/,
        use: [
          'file-loader',
        ]
      },
      { test: /\.js$/,
        exclude: /node_modules/,
        loader: "babel-loader",
        options: {   // options里面的内容也可以都提出去,提到根目录下的.babelrc,就不需要在这里写了
          // presets: [["@babel/preset-env", {
          //   targets: {
          //     chrome: "58",               // 如果chrome 58版本支持,就不去转换了
          //     ie: "11"
          //   },
          //   useBuiltIns: "usage",     // 会帮你自动引入 @babel/polyfill
          //   "corejs": "3"
          // }]]
          plugins: [["@babel/plugin-transform-runtime", {  // 在写类库的时候可以这么配置,避免全局引入@babel/polyfill从而造成数据污染,此方式会以闭包等注入有作用域,不会污染到其他地儿
            "absoluteRuntime": false,
            "corejs": 2,    //  当页面需要转换语法的时候帮你装换
            "helpers": true,
            "regenerator": true,
            "useESModules": false,
            "version": "7.0.0-beta.0"
          }]]
        }
      }
    ]
  }
}

安装react打包需要的环境
npm i react react-dom --save npm install --save-dev @babel/preset-react

react 代码

import React, { Component } from 'react';
import ReactDom from 'react-dom';

class App extends Component {
  render() {
    return 
hello react
} } ReactDom.render(, document.getElementById('app'));

npm install --save-dev @babel/preset-react

Tree Shaking

Tree Shaking 只支持ES Module的引入方式(底层静态引入,而CommonJS引入属于动态引入,tree shaking只支持静态引入),他会把,你项目中没有用到的模块剔除掉,mode: 'production'此环境自动就配置了

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const webpack = require('webpack');

module.exports = {
  mode: 'development',
  devtool: 'cheap-module-eval-source-map',    // 参打开代码映射,参数none 关闭
  entry: {
    main: './src/index.js',
    sub: './src/index.js'
  },
  devServer: {
    contentBase: './dist',
    open: true,
    hot: true,
    hotOnly: true   // 如果没有写HMR 热模块相关的代码,可以把此配置去掉,自动刷新浏览器
  },
  plugins: [
    new CleanWebpackPlugin(['dist'],{
      root: path.resolve(__dirname, '/),   // 设置要删除文件的根目录
       cleanStaleWebpackAssets: false 
    }),
    new HtmlWebpackPlugin({
      title: 'Development',
      template: 'src/index.html'
    }),
    new webpack.HotModuleReplacementPlugin()   // HMR 热模块替换
  ],
  // 在 package.json中配置 "sideEffects": [ '*.css', '@babel/polyfill' ],  // 避免那种直接import引入的文件被忽略,如 import '@babel/polyfill'
  optimization: {   // tree shaking 支持,production 模式中只不需要配置这块,只需要package.json中配置 "sideEffects"
    usedExports: true
  },
  output: {
    publicPath: '/',        // 指向跟目录。对应server.js   一个node服务 
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif)$/i,
        use: {
          loader: 'url-loader',
          options: {
            name: '[name]_[hash].[ext]',
            outputPath: 'images/',
            limit: 2048
          }
        }
      },
      {
        test: /\.scss$/i,
        use: [
          'style-loader',
          { 
            loader: 'css-loader',
            options: {
              importLoaders: 2,
              // modules: true
            } 
          },
          'sass-loader',
          'postcss-loader'
        ]
      },
      {
        test: /\.(eot|woff2|woff|ttf|svg)$/,
        use: [
          'file-loader',
        ]
      },
      { test: /\.js$/,
        exclude: /node_modules/,
        loader: "babel-loader",
        // options: {   // options里面的内容也可以都提出去,提到根目录下的.babelrc,就不需要在这里写了
        //   // presets: [["@babel/preset-env", {
        //   //   targets: {
        //   //     chrome: "58",               // 如果chrome 58版本支持,就不去转换了
        //   //     ie: "11"
        //   //   },
        //   //   useBuiltIns: "usage",
        //   //   "corejs": "3"
        //   // }]]
        //   plugins: [["@babel/plugin-transform-runtime", {  // 在写类库的时候可以这么配置,避免全局引入@babel/polyfill从而造成数据污染,此方式会以闭包等注入有作用域,不会污染到其他地儿
        //     "absoluteRuntime": false,
        //     "corejs": 2,
        //     "helpers": true,
        //     "regenerator": true,
        //     "useESModules": false,
        //     "version": "7.0.0-beta.0"
        //   }]]
        // }
      }
    ]
  }
}

development 和 production 模式的区分打包

可以创建 webpack.dev.js 和 webpack.prod.js 两个配置文件来区分开发和生产,通过package.json里面配置命令 --config webpack.dev.js 等等
将一些公用的代码,提取到webpack.common.js文件中,避免冗余

npm install webpack-merge -D通过这个插件,将webpack.common.js中的代码,分别和dev和prod中的代码进行合并

const merge = require('webpack-merge)
const commonConfig = require('./webpack.common.js')
...
module.exports = merge(commonConfig, devConfig)

webpack 和 Code Splitting 代码分割

如果代码不分割,打包的体积会很大,加载速度变慢,比如一些插件库 loadsh 等是不需要被时刻打包的,手动代码分割,可以自entry里面做多个打包入口,这样就可以打包成多个文件

自动配置的话,可以在之前的optimization里面做一些配置
代码分割和webpack没有任何关系,只是webpack 可以更优雅的实现代码分割

 optimization: {
  splitChunks: {
      chunks: 'all'
  }
}

异步导入 实验性语法,需要用babel去解析,安装npm install babel-plugin-dynamic-import-webpack -D
.babelrc 配置

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "chrome": "58",
          "ie": "11"
        },
        "useBuiltIns": "usage",
        "corejs": "3"
      }
    ],
    "@babel/preset-react"
  ],
  plugins: ['dynamic-import-webpack']
}

/* webpackPrefetch: true */ 表示网络空闲时开始加载,首屏渲染优化点,这样点击事件触发的时候,不会有体验问题,应用场景,首页有登录弹窗的时候,可以把,登录框采用异步加载,优化首屏渲染速度

function getComponent() {
  return import(/* webpackPrefetch: true */'loadsh').then(({ default: _ }) => {
    var element = document.createElement('div');
    element.innerHTML = _.join([1,2,3], '-')
  })
}
document.addEventListener('click',() => {
  getComponent().then(element => {
    document.body.appendChild(element) 
  })
})

webpack 会对同步和异步载入的代码,都会进行分割(默认参数async 只对异步代码进行分割,因为webpack更多希望你在代码书写的时候就采用异步,优雅的代码)。代码分割,和webpack无关,webpack实现代码分割的方式有两种,1同步代码的分割,只需要在webpack配置文件中配置,optimization的配置,2异步代码,无需任何配置,会自动进行代码的分割

splitChunksPlugin 配置参数的详解

使用官方提供的异步导入兼容插件
npm install --save--dev @babel/plugin-syntax-dynamic-import

更改chunk名字,可以使用注释的写法 /* webpackChunkName: 'loadsh' */

默认配置

module.exports = {
  /...
// todo 待后续仔细研究
  optimization: {   // tree shaking 支持,production 模式中只不需要配置这块,只需要package.json中配置 "sideEffects"
    usedExports: true,
    splitChunks: {
      chunks: 'async',     // 只对异步代码生效  参数all 同步异步都会分割
      minSize: 20000,   // 大于 20000 字节才去做分割
      minRemainingSize: 0,
      maxSize: 9000000,   // 如果被分割的代码大于9000000 字节,会对这些文件尝试进行二次分割
      minChunks: 1,   // 代码被使用多少次之后,才进行代码分割
      maxAsyncRequests: 30,   // 同时加载的模块最多是30个,超过30个就不会做代码分割了
      maxInitialRequests: 30,  // 入口文件,最多可以分割30个,多了就不在分割 
      automaticNameDelimiter: '~',  // 打包出文件,组合文件名的连接符
      enforceSizeThreshold: 50000,
      cacheGroups: {   // 同步代码会走这里,如果是node-modules下的会分割在defaultVendors组中, cacheGroups和 chunks配合使用
        defaultVendors: {  // cacheGroups 缓存组,打包多个文件的时候,先放在缓存组里面,最后再合并输出
          test: /[\\/]node_modules[\\/]/,
          priority: -10,    // 多个组之间的匹配规则,值越大优先级越高
          filename: 'vendors.js'
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true,    // 如果之前这个模块被打包过,那么再次打包的时候就会忽略这个模块使用之前打包好的
          filename: 'common.js'
        }
      }
    }
  },

Lazy Loading 懒加载,Chunk是什么

打包分析,Preloading Prefetching

webpack.github.com/analyse
将打包的描述文件放在stats.json中
"build": "webpack --profile --json > stats.json",
将输出的文件上传到 webpack.github.com/analyse中会查看打包的一些分析依赖

首屏渲染优化

尽量去写,异步加载的代码,提高代码的利用率,覆盖率 commd + shif + p 搜索 show cover 进行浏览器覆盖率调试
/* webpackPrefetch: true */ 表示网络空闲时开始加载,首屏渲染优化点
webpackPrefetch webpackPreLoad 的区别 webpackPrefetch会等待页面加载完之后页面空闲的时候再加载你的代码,webpackPreLoad不会等待会一起加载,所以还是webpackPrefetch比较合适(注意浏览器的兼容)

Css文件的代码分割

Css文件的代码分割 MiniCssExtractPlugin替换style-loader, 在使用此插件做css 代码分割的时候,一定要主要的是tree shaking 中要排除 *.css 一面,import引入的css 被忽略

Webpack与浏览器缓存(Caching)

contenthash 如果打包出的文件没有变化此值不变,有变化才会变

output: {
  filename: '[name].[contenthash].js',
  chunkFilename: '[name].[contenthash].js'
}  
// 兼容低版本,防止contenthash不改变内容也变
// 低版本中对于各个js之间的依赖关系(manifest)有可能会变,所以配置如下,将存在与各个js之间的依赖关系提取出来,这样打包会多生成一个runtime.js的文件
optimization: {   
  runtimeChunk: {
    name: 'runtime'
  }
}

Shimming 垫片

假如你引用的一个npm包依赖另一个文件,而犹豫webpack的模块化打包,你在外层引入这个依赖文件是不能被npm包所找到,这时候就需要用到,垫片 webpack 提供的一个插件webpack.ProvidePlugin

// 当你的模块中使用到$这个变量的时候,这个插件会在编译的过程中自动帮你引入jquery,给你自动添加类似  `import $ from 'jquery'` 这样的代码
new webpack.ProvidePlugin({
  $: 'jquery',
  _join: ['loadsh', 'join']     // 当你使用_join的时候webpack会自动帮你吧 loadsh中的join模块引入进去赋值给_join
})

一个模块中的this始终指向模块自身,其上下文,如果我想让一个模块中的this指向window呢该怎么做可以借助插件 npm install imports-loader --save-dev

module: {
  rules: [{
    test: /\.js$/,
    exclude: /node_modules/,
    use: [
      {
         loader: 'babel-loader'
      },
      {
        loader: 'imports-loader?this=>window'     //  改变了webpack的一些默认行为
      }
    ]
  }]
}

环境变量的使用

module.exports = (env) => {
  if(env && env.production) { // 生产
    return merge(commonConfig, prodConfig);
  }else { // 开发
    return merge(commonConfig, devConfig);
  }
}
"build": "webpack --env.production --config ./build/webpack.common.js"  // 传递一个env.production全局变量

Library 的打包

上一篇webpack初识

你可能感兴趣的:(webpack模块打包 使用 构建)