adodo0829 / blog

搭建知识体系
29 stars 4 forks source link

webpack读书小结 #72

Open adodo0829 opened 4 years ago

adodo0829 commented 4 years ago

webpack读书小结

本篇主要是针对 webpack 文档的读书笔记, 以目录的形式展现, 旨在对 webpack 有个全局的了解...使用的时候不至于那么陌生emm...持续更新中ing

1.webpack简介

学一个知识点或者某一领域的内容, 遵守这个知识点是什么(what); 为什么用它(why); 怎么用它(how); 什么场景下用它(who); 使用时候的注意事项,坑点(when),当然这是事后总结...这是我一贯的学习习惯

导出: 模块内部有个 moudle 对象(记录每个模块的状态), 有个exports属性,用来导出 module.exports(他也是一个引用) = {}, 或者exports.xxx = ooo(exports引用module.exports)

导入: require('./module.js') require(): 它是一个函数, 执行()中的路径参数, 从而去执行路径模块文件的内容 再次调用(module.loaded = true): 直接去上次调用后缓存的结果

- ESModule规范
他是js语言级别的特性,有特定关键字语法实现,在解析编译时确定模块(引用);
同样也是文件即模块, 默认严格模式

作用域: 模块自身

导出: export 语法 具名导出: export const name = 'huhua', export 变量, export { name as 改个名导出 } 默认导出: export defalut 你想导出的变量或者确定值; 模块挂载 default 属性上

导入: import 语法 导入具名模块: import { name } from './info.js' 导入默认模块: import Name from './infoDefault.js' 整体导入(当模块导出多个变量时): import * as ModuleObj from './info.js' 复合导入: import XXX, { ooo } from 'module' ...不说了 自执行导入: import './autoDo.js'

- 二者的区别

1.加载方式: CommonJS: 在代码运行时去查找,运行模块依赖文件(动态的) ESModule: 在代码解析,编译阶段查找模块依赖文件(静态的, 针对这一特性: 便有了tree shaking等一系列优化措施)

2.导入结果: CommonJS: 导入的是一个模块执行结果的拷贝 ESModule: 导入的是一个模块属性的只读引用

3.循环依赖解决: 项目太大时, 代码可能存在隐式的模块依赖循环引用 比如: a.js依赖b.js, b.js又依赖a.js CommonJS: 执行逻辑: a -> b(引入a: 直接导出a,未执行完: 空{}, 继续执行b) -> b 执行完在执行 a 解决: 模块是值拷贝, 无法解决, 只能提前返回一个{} ESModule: 执行逻辑: a -> b(引入a: 直接导出a,未执行完: undefined, 继续执行b) -> b 执行完在执行 a 解决: 因为模块是动态引用, 利用这个特性可以解决

- UMD: 通用模块标准, 根据执行环境运行模块
```js
(function (global, main) {
  if (typeof define === 'function' && define.amd) {
    // AMD module
    define(fn())
  } else if (typeof exports === 'object') {
    // commonjs module
    module.exports = { fn }
  } else {
    // 浏览器环境
    global.fn = fn
  }
})(this, function() { return { /*模块 */ } })

输入: 入口配置

module.exports = {
  // 工程根目录路径
  context: path.join(__dirname, './src') 
  // 字符串路径
  entry: './index.js' 
  // 数组:资源预先合并(导入), 末尾为入口
  entry: ['babel-polyfill', './index.js']
  // entry为对象时, 用于定义多入口
  entry: {
     index: './index.js',
     lib: './lib/index.js'
  }
  // entry 为函数时, 返回值为上面配置即可
  entry: () => {
    return {
      index: './index.js',
      lib: './lib/index.js'
    }
  }
}

// 多页应用 module.exports = { entry: { page1: './src/page1.js', page2: './src/page2.js', page3: './src/page3.js', vendor: ['vue', 'vue-router'] // 公共第三方模块, 需配合optimization.splitChunks } // 打完包后, 各自的 html 引入对应的 chunk 即可 }

#### 输出: 出口文件
```js
// 单入口
module.exports = {
  entry: './src/index.js',
  // 配置输出对象
  output: {
    filename: 'bundle.js', // 输出文件名
    path: path.join(__dirname, 'dist'), // 项目资源输出的目录
    publicPath: '/dist/' // 指定打包资源的请求路径
    // 如果我们打包后文件不是放在根域名下, 需要特别注意一下,曾经踩过坑

    // 1.资源文件在 html 中加载, publicPath表示index.html所在的相对路径的拼接
    // 假设 index.html资源访问路径为 http://xxx.com/dist/index.html
    // 引入的js文件名为 chunk0.js
    publicPath: '' // 访问路径为: http://xxx.com/dist/chunk0.js
    publicPath: './js' // 访问路径为: http://xxx.com/dist/js/chunk0.js
    publicPath: '../assets/' // 访问路径为: http://xxx.com/assets/chunk0.js

    // 2.publicPath为 /: 以当前的 host 为基础路径 进行拼接
    publicPath: '/' // http://xxx.com/chunk0.js
    publicPath: '/js' // http://xxx.com/js/chunk0.js

    // 3.CDN 路径, 拼接 cdn 绝对路劲
    publicPath: 'https://cdn1.com/' // https://cdn1.com/chunk0.js

    // 开发环境下: webpack-dev-server 也有一个配置, 表示静态资源的位置
    devServer: {
      publicPath: '/dist/',
      port: 8080
    }
  }
}

// 多入口时
module.exports = {
  entry: {
    app: './src/index.js',
    lib: './src/lib.js'
  },
  output: {
    // 动态生成输出文件名, 对应上面的chunk name
    // 生成环境下加上hash值, 可用于清除静态资源缓存
    filename: '[name].js' // [name].[chunkhash].js
  }
}

4.webpack 预处理器 loader: 处理各类资源文件模块

我们一个工程目录下往往并不是只有 js 文件, 还有其他资源文件, 如 html, css,图片,字体等... 他们又不是标准的模块, 那我们打包怎么处理, 所以引入预处理器 loader 来负责.

loader 简介

loader本质上是一个函数

output = loader(input)
// 支持链式调用, 可以理解为管道操作
output = loader1(loader2(loader3(input)))

// 示例
function loader(content, map, meta) {
  var cb = this.async()
  var result = handler(content, map, meta) // 处理资源
  cb(
    null,
    result.content, // 处理后的内容
    result.map,     // 处理后的 map
    result.meta     // 处理后的 AST
  )
}

loader 配置

module.exports = {
  // 省略...
  module: {
    rules: [
      {
        // 处理 .css类型的模块文件
        test: /\.css$/,     
        // 使用哪些laoder处理, loader 机制从后往前处理
        use: [
          'style-loader', // loader1
          {
            loader: 'css-loader',
            // options 传入自己的配置
            options: {

            },
          }.
          // 指定使用 loader 的目录
          exclude: '/src\/lib/', // src/lib下不使用, 优先级高
          include: '/src/'       // src 下都使用

          // resource 和 issuer 可以更精确控制加载的文件使用loader
        ],
      }
    ]
  }
}

项目常用 loader

// babel-loader 支持.babelrc 文件读取 babel 配置, 可以抽离出来 { "presets": [ ["env", { "modules": false, "targets": { "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] } }], "stage-2", ], "plugins": ["transform-runtime"] } // 也支持 babel.config.js module.exports = { presets: [ '@vue/app' ] }

- ts-loader
```js
// 打包 ts 文件
rules: [
  {
    test: /\.ts$/,
    use: 'ts-loader'
  }
]
// 项目根目录下配置 tsconfig.json
// 具体去参考官网
{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "strict": true,
    "jsx": "preserve",
    "importHelpers": true,
    "moduleResolution": "node",
    "experimentalDecorators": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "allowJs": true,
    "sourceMap": true,
    "baseUrl": ".",
    "types": [
      "node",
      "jest",
      "webpack-env"
    ],
    "paths": {
      "@/*": [
        "src/*"
      ]
    },
    "lib": [
      "esnext",
      "dom",
      "dom.iterable",
      "scripthost"
    ]
  },
  "include": [
    "src/**/*.ts",
    "src/**/*.tsx",
    "src/**/*.vue",
    "tests/**/*.ts",
    "tests/**/*.tsx"
  ],
  "exclude": [
    "node_modules"
  ]
}

import aHtml from './a.html' document.write(aHtml)

- file-loader
```js
// 打包文件资源, 返回 publicPath 资源引用路径
rules: [
  {
    test: '/\.(png|jpg|gif)$/',
    use: {
      loader: 'file-loader',
      options: {
        name: '[name].[ext]',
        publicPath: './assets/' // 默认与工程的 publicPath一致
      }
    }
  }
]

import imgPath from './images/xx.png'
console.log(imgPath) // ./assets/hash.png

import imgPath from './images/xx.png' console.log(imgPath) // data:image/png base64字符

- vue-loader
```html
<!-- 处理 .vue 文件 -->
<template>
  <div> {{ msg }} </div>
</template>
<script>
export default {
  data() {
    return {
      msg: 'vue'
    }
  } 
}
</script>
<style lang="css">
div {
  color: red
}
</style>

<!--
rules: [
  {
    test: '/\.vue$/',
    use: 'vue-loader'
  }
]
-->

自定义的 loader 函数

loader API

function someloader(content) {
  // content: 资源文件(resource file)的内容
  function dosomething(file) {
    // ....
  }
  // 1.同步 loader
  return dosomethig(content)

  // 2.异步 loader
  var cb = this.async()
  someAsyncOperation(content, function(err, result) {
    if (err) return callback(err);
    callback(null, result, map, meta);
  });
}

5.webpack 插件 Plugins

Plugins 用于 bundle 文件的优化,资源管理和环境变量注入,在整个构建过程起作用

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;
// 常用插件
// CommonsChunkPlugin 将 chunks 相同的模块代码提取成公共 js
// CleanWebpackPlugin 清理构建目录
// ExtractTextWebpackPlugin 将 CSS 从 bundle 文件中提取成一个独立的 CSS 文件
// CopyWebpackPlugin 将文件或者文件夹拷贝到构建的输出目录
// HTMLWebpackPlugin 创建 html 文件去承输出的 bundle
// UglifyjsWebpackPlugin 压缩 JS
// ZipWebpackPlugin 将打包出的资源生成一个 zip 包

6.代码分片

也就是将打包的代码分成小块, 在访问页面时加载必要的资源, 其他资源可以延迟加载或者渐进式的加载... 说了那么多, 即按需加载必要代码

怎么去划分和管理代码块

// SplitChunks module.exports = { entry: './index.js', output: { filename: 'bundle.js', publicPath: '/dist/' }, optimization: { splitChunks: { chunks: 'all' } } }

// optimization.splitChunks默认配置 splitChunks: { chunks: "async", // 工作模式: 提取异步 chunk // chunk匹配条件 minSize: 30000, minChunks: 1, maxAsyncRequests: 5, maxInitialRequests: 3, automaticNameDelimiter: '~', // 分隔符 name: true, // chunk 分离规则cacheGroups && default cacheGroups: { vendors: { // 作用于所有node_modules中符合条件的模块 test: /[\/]node_modules[\/]/, priority: -10 }, default: { // 作用于被多次引用的模块 minChunks: 2, priority: -20, reuseExistingChunk: true } } }

- 按需异步加载文件: import函数  
通过import函数加载的模块及其依赖会被异步地进行加载,并返回一个Promise对象
```js
// loading.js
export function start() {
  console.log('page is loading')
}

import('./loading.js').then(({ start }) => {
  start()
})
// 通过js在页面的head标签里插入一个script标签/dist/chunkName.js

// webpack.config.js
output: {
    chunkFilename: ('[name].js') //指定异步chunk的文件名
  },

import(/* webpackChunkName: 'start' */ './start.js')
.then((module) => {
  module.start()
})

生产环境打包配置注意

1.环境变量:
通过判断 process.env.NODE_ENV
2.source map 配置
3.代码压缩 uglify(js,css)
4.缓存文件更新, hash name设置
5.动态 html改变, htmlWebpackPlugin
6.bundle.js 体积大小监控

项目构建打包优化

// 对 babel 编译的 AST 针对上述特性做处理 // 修改 AST

['@babel/preset-env', { module: false }]


## 本地开发优化
- webpack-merge合并公共配置文件
- 模块热替换 HMR
```js
{
  // ....
  devServer: {
    hot: true
  }
}
// 核心: 资源文件更新,加载, chunk diff
// dev server: 监听文件更新, ws发送通知, 和文件 hash 值
// client: 校对hash 值, 发送变更资源的请求[hash].hot-update.json
adodo0829 commented 4 years ago

add