# webpack复习

在js中引入加了括号后会单独生成一个js,不加括号的import则会被打包到一个js中!!!! import "../css/index.css" import ("../styl/default.styl")

# webpack 图片处理

  • url-loader依赖于file-loader
  • html-loader是处理html文件中的图片
module.exports = {
  
  module: {
    rules: [
      {
        test: /\.less$/,
        // 要使用多个loader处理用use
        use: ['style-loader', 'css-loader', 'less-loader']
      },
      {
        // 问题:默认处理不了html中img图片
        // 处理图片资源
        test: /\.(jpg|png|gif)$/,
        // 下载 url-loader file-loader
        loader: 'url-loader',
        options: {
          // 图片大小小于8kb,就会被base64处理
          // 优点: 减少请求数量(减轻服务器压力)
          // 缺点:图片体积会更大(文件请求速度更慢)
          limit: 8 * 1024,
          // 问题:因为url-loader默认使用es6模块化解析,而html-loader引入图片是commonjs
          // 解析时会出问题:[object Module]
          // 解决:关闭url-loader的es6模块化,使用commonjs解析
          esModule: false,
          // 给图片进行重命名
          // [hash:10]取图片的hash的前10位
          // [ext]取文件原来扩展名
		  //可以使用[name].[ext]直接使用原文件名
          name: '[hash:10].[ext]',
		  outputPath:'img/',//输出路径,在img文件夹下
        }
      },
      {
        test: /\.html$/,
        // 处理html文件的img图片(负责引入img,从而能被url-loader进行处理)
        loader: 'html-loader'
      }
    ]
  }

};

# webpack其他文件

如字体图标

    rules: [
      {
        exclude: /\.(css|js|html|less)$/,
        loader: 'file-loader',
        options: {
          name: '[hash:10].[ext]'
        }
      }
    ]

# webpack处理vue

  1. vue(.runtime).global(.prod).js:
  • 通过浏览器中的 <script src="..."> 直接使用;
  • 我们之前通过CDN引入和下载的Vue版本就是这个版本;
  • 会暴露一个全局的Vue来使用;
  1. vue(.runtime).esm-browser(.prod).js:
  • 用于通过原生 ES 模块导入使用 (在浏览器中通过 <script type="module"> 来使用)。
  1. vue(.runtime).esm-bundler.js:
  • 用于 webpack,rollup 和 parcel 等构建工具;
  • 构建工具中默认是vue.runtime.esm-bundler.js;
  • 如果我们需要解析模板template,那么需要手动指定vue.esm-bundler.js;
  1. vue.cjs(.prod).js:
  • 服务器端渲染使用;
  • 通过require()在Node.js中使用;

默认使用的vue不含编译器,所以使用template会失败,可以引用如下的vue包含编译器的版本即可成功打包

真实开发中多数情况下都是使用SFC( single-file components (单文件组件) )。

写了App.vue文件,因为webpack不认识.vue,也无法处理里面的template等,需要加装loaders处理

cnpm install vue-loader @vue/compiler-sfc -D

@vue/compiler-sfc vue2中名称不是这样,如果需要维护老项目需要注意

  • vue-loader正常使用还需要配置一个plugin,在vue-loader/dist/index中
const path = require("path");
const { DefinePlugin } = require("webpack");
const { VueLoaderPlugin } = require('vue-loader/dist/index');

module.exports = {
  entry: "./src/main.js",
  output: {
    path: path.resolve(__dirname, "./build"),
    filename: "js/bundle.js",
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        loader: "babel-loader"
      },
      {
        test: /\.vue$/,
        loader: "vue-loader"
      }
    ],
  },
  plugins: [
    new DefinePlugin({
      BASE_URL: "'./'",
      __VUE_OPTIONS_API__: true,
      __VUE_PROD_DEVTOOLS__: false
    }),
    new VueLoaderPlugin()
  ],
};

# webpack一个比较完整的配置

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

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      // loader的配置
      {
        test: /\.less$/,
        use: ['style-loader', 'css-loader', 'less-loader']
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      },
      {
        test: /\.(jpg|png|gif)$/,
        loader: 'url-loader',
        options: {
          limit: 8 * 1024,
          name: '[hash:10].[ext]',
          esModule: false,
          outputPath: 'imgs'
        }
      },
      {
        test: /\.html$/,
        loader: 'html-loader'
      },
      {
        exclude: /\.(html|js|css|less|jpg|png|gif)/,
        loader: 'file-loader',
        options: {
          name: '[hash:10].[ext]',
          outputPath: 'media'
        }
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ],
  mode: 'development',
  devServer: {
    contentBase: resolve(__dirname, 'build'),
    compress: true,
    port: 3000,
    open: true
  }
};

# webpack之css压缩

插件optimize-css-assets-webpack-plugin

const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
module.exports = {
  plugins: [
    // 压缩css
    new OptimizeCssAssetsWebpackPlugin()
  ]
};

# webpack之babel

三种方式

  • @babel/preset-env只能处理基本兼容
  • @babel/polyfill解决所有,但是包体积大
  • core-js按需加载
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        options: {
          // 基本方式
          presets: [
              '@babel/preset-env',
          ]
        }
      }
//引入安装的@babel/polyfill
import '@babel/polyfill';

const add = (x, y) => {
  return x + y;
};
console.log(add(2, 5));

const promise = new Promise(resolve => {
  setTimeout(() => {
    console.log('定时器执行完了~');
    resolve();
  }, 1000);
});

console.log(promise);

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

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      /*
        js兼容性处理:babel-loader @babel/core 
          1. 基本js兼容性处理 --> @babel/preset-env
            问题:只能转换基本语法,如promise高级语法不能转换
          2. 全部js兼容性处理 --> @babel/polyfill  
            问题:我只要解决部分兼容性问题,但是将所有兼容性代码全部引入,体积太大了~
          3. 需要做兼容性处理的就做:按需加载  --> core-js
      */  
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        options: {
          // 预设:指示babel做怎么样的兼容性处理
          presets: [
            [
              '@babel/preset-env',
              {
                // 按需加载
                useBuiltIns: 'usage',
                // 指定core-js版本
                corejs: {
                  version: 3
                },
                // 指定兼容性做到哪个版本浏览器
                targets: {
                  chrome: '60',
                  firefox: '60',
                  ie: '9',
                  safari: '10',
                  edge: '17'
                }
              }
            ]
          ]
        }
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ],
  mode: 'development'
};

注意

对装饰器还需额外配置,方法之一在package.json中添加 安装cnpm i @babel/plugin-proposal-decorators --save-dev

"babel": {
	"plugins": [
	    ["@babel/plugin-proposal-decorators", { "legacy": true }]
	 ]
  }

# webpack之html js压缩

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

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
      // 压缩html代码
      minify: {
        // 移除空格
        collapseWhitespace: true,
        // 移除注释
        removeComments: true
      }
    })
  ],
  mode: 'production'//生产环境自动压缩js
};

# webpack一个比较完整的生产环境

const { resolve } = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

// 定义nodejs环境变量:决定使用browserslist的哪个环境
process.env.NODE_ENV = 'production';

// 复用loader
const commonCssLoader = [
  MiniCssExtractPlugin.loader,
  'css-loader',
  {
    // 还需要在package.json中定义browserslist
    loader: 'postcss-loader',
    options: {
      ident: 'postcss',
      plugins: () => [require('postcss-preset-env')()]
    }
  }
];

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [...commonCssLoader]
      },
      {
        test: /\.less$/,
        use: [...commonCssLoader, 'less-loader']
      },
      /*
        正常来讲,一个文件只能被一个loader处理。
        当一个文件要被多个loader处理,那么一定要指定loader执行的先后顺序:
          先执行eslint 在执行babel
      */
      {
        // 在package.json中eslintConfig --> airbnb
        test: /\.js$/,
        exclude: /node_modules/,
        // 优先执行
        enforce: 'pre',
        loader: 'eslint-loader',
        options: {
          fix: true
        }
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        options: {
          presets: [
            [
              '@babel/preset-env',
              {
                useBuiltIns: 'usage',
                corejs: {version: 3},
                targets: {
                  chrome: '60',
                  firefox: '50'
                }
              }
            ]
          ]
        }
      },
      {
        test: /\.(jpg|png|gif)/,
        loader: 'url-loader',
        options: {
          limit: 8 * 1024,
          name: '[hash:10].[ext]',
          outputPath: 'imgs',
          esModule: false
        }
      },
      {
        test: /\.html$/,
        loader: 'html-loader'
      },
      {
        exclude: /\.(js|css|less|html|jpg|png|gif)/,
        loader: 'file-loader',
        options: {
          outputPath: 'media'
        }
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'css/built.css'
    }),
    new OptimizeCssAssetsWebpackPlugin(),
    new HtmlWebpackPlugin({
      template: './src/index.html',
      minify: {
        collapseWhitespace: true,
        removeComments: true
      }
    })
  ],
  mode: 'production'
};

{
  "name": "webpack_code",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.8.4",
    "@babel/polyfill": "^7.8.3",
    "@babel/preset-env": "^7.8.4",
    "add-asset-html-webpack-plugin": "^3.1.3",
    "babel": "^6.23.0",
    "babel-loader": "^8.0.6",
    "core-js": "^3.6.4",
    "css-loader": "^3.4.2",
    "eslint": "^6.8.0",
    "eslint-config-airbnb-base": "^14.0.0",
    "eslint-loader": "^3.0.3",
    "eslint-plugin-import": "^2.20.1",
    "file-loader": "^5.0.2",
    "html-loader": "^0.5.5",
    "html-webpack-plugin": "^3.2.0",
    "less": "^3.11.1",
    "less-loader": "^5.0.0",
    "mini-css-extract-plugin": "^0.9.0",
    "optimize-css-assets-webpack-plugin": "^5.0.3",
    "postcss-loader": "^3.0.0",
    "postcss-preset-env": "^6.7.0",
    "style-loader": "^1.1.3",
    "terser-webpack-plugin": "^2.3.5",
    "thread-loader": "^2.1.3",
    "url-loader": "^3.0.0",
    "webpack": "^4.41.6",
    "webpack-cli": "^3.3.11",
    "webpack-dev-server": "^3.10.3",
    "workbox-webpack-plugin": "^5.0.0"
  },
  "dependencies": {
    "jquery": "^3.4.1"
  },
  "browserslist": {
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ],
    "production": [
     ">0.2%",
     "not dead",
     "not op_mini all"
    ]
  },
  "eslintConfig": {
    "extends": "airbnb-base",
    "env": {
      "browser": true
    }
  },
  "sideEffects": [
    "*.css"
  ]
}

# webpack优化配置

# source-map

source-map: 一种 提供源代码到构建后代码映射 技术 (如果构建后代码出错了,通过映射可以追踪源代码错误)

    [inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map

    source-map:外部
      错误代码准确信息 和 源代码的错误位置
    inline-source-map:内联
      只生成一个内联source-map  错误代码准确信息 和 源代码的错误位置
    hidden-source-map:外部
      错误代码错误原因,但是没有错误位置  不能追踪源代码错误,只能提示到构建后代码的错误位置
    eval-source-map:内联
      每一个文件都生成对应的source-map,都在eval  错误代码准确信息 和 源代码的错误位置
    nosources-source-map:外部
      错误代码准确信息, 但是没有任何源代码信息
    cheap-source-map:外部
      错误代码准确信息 和 源代码的错误位置 只能精确的行
    cheap-module-source-map:外部
      错误代码准确信息 和 源代码的错误位置 
      module会将loader的source map加入

    内联 和 外部的区别:1. 外部生成了文件,内联没有 2. 内联构建速度更快

    开发环境:速度快,调试更友好
      速度快(eval>inline>cheap>...)
        eval-cheap-souce-map
        eval-source-map
      调试更友好  
        souce-map
        cheap-module-souce-map
        cheap-souce-map
      --> eval-source-map  / eval-cheap-module-souce-map

    生产环境:源代码要不要隐藏? 调试要不要更友好
      内联会让代码体积变大,所以在生产环境不用内联
      nosources-source-map 全部隐藏
      hidden-source-map 只隐藏源代码,会提示构建后代码错误信息
      --> source-map / cheap-module-souce-map
module.exports = {
  devtool: 'eval-source-map'
};

# webpack之缓存

  • babel缓存
    • cacheDirectory: true --> 让第二次打包构建速度更快
  • 文件资源缓存
    • hash: 每次wepack构建时会生成一个唯一的hash值。 问题: 因为js和css同时使用一个hash值。 如果重新打包,会导致所有缓存失效。(可能只改动一个文件)
    • chunkhash:根据chunk生成的hash值。如果打包来源于同一个chunk,那么hash值就一样 问题: js和css的hash值还是一样的 因为css是在js中被引入的,所以同属于一个chunk
    • contenthash: 根据文件的内容生成hash值。不同文件hash值一定不一样
      --> 让代码上线运行缓存更好使用
// 定义nodejs环境变量:决定使用browserslist的哪个环境
process.env.NODE_ENV = 'production';
module.exports = {

  module: {
    rules: [    
          {
            test: /\.js$/,
            exclude: /node_modules/,
            loader: 'babel-loader',
            options: {
              presets: [
                [
                  '@babel/preset-env',
                  {
                    useBuiltIns: 'usage',
                    corejs: { version: 3 },
                    targets: {
                      chrome: '60',
                      firefox: '50'
                    }
                  }
                ]
              ],
              // 开启babel缓存
              // 第二次构建时,会读取之前的缓存
              cacheDirectory: true
            }
          },

    ]
  }

};

# tree-shaking

tree shaking:去除无用代码

  • 前提:1. 必须使用ES6模块化 2. 开启production环境

  • 作用: 减少代码体积

  • 在package.json中配置

    • "sideEffects": false 所有代码都没有副作用(都可以进行tree shaking) 问题:可能会把css / @babel/polyfill (副作用)文件干掉
    • "sideEffects": [".css", ".less"]

# webpack code-split

  • 多入口,可以将js分多个而不是原来的一个js
module.exports = {
  // 单入口
  // entry: './src/js/index.js',
  entry: {
    // 多入口:有一个入口,最终输出就有一个bundle
    index: './src/js/index.js',
    test: './src/js/test.js'
  }

};

  • 将node_modules中的文件打包成一个单独的js optimization
module.exports = {
  /*
    1. 可以将node_modules中代码单独打包一个chunk最终输出
    2. 自动分析多入口chunk中,有没有公共的文件。如果有会打包成单独一个chunk
  */
  optimization: {
    splitChunks: {
      chunks: 'all'
    }
  }
};

  • import动态导入语法 webpack配置与上面相同 这种模式import还可以抛出异常

/*
  通过js代码,让某个文件被单独打包成一个chunk
  import动态导入语法:能将某个文件单独打包
*/
import(/* webpackChunkName: 'test' */'./test')
  .then(({ mul, count }) => {
    // 文件加载成功~
    console.log(mul(2, 5));
  })
  .catch(() => {
    // eslint-disable-next-line
    console.log('文件加载失败~');
  });


# webpack之预加载懒加载

这里的预加载懒加载是文件而不是页面上的请求图片之类的,目前预加载兼容差,懒加载则比较适应

console.log('index.js文件被加载了~');

// import { mul } from './test';

document.getElementById('btn').onclick = function() {
  // 懒加载~:当文件需要使用时才加载~
  // 预加载 prefetch:会在使用之前,提前加载js文件 
  // 正常加载可以认为是并行加载(同一时间加载多个文件)  
  // 预加载 prefetch:等其他资源加载完毕,浏览器空闲了,再偷偷加载资源
  import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test').then(({ mul }) => {
    console.log(mul(4, 5));
  });
};

# webpoack之pwa

Service Worker 是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。

//webpack配置
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
new WorkboxWebpackPlugin.GenerateSW({
  /*
	1. 帮助serviceworker快速启动
	2. 删除旧的 serviceworker
	生成一个 serviceworker 配置文件~
  */
  clientsClaim: true,
  skipWaiting: true
})
/*
    sw代码必须运行在服务器上
      --> nodejs
      -->
        npm i serve -g
        serve -s build 启动服务器,将build目录下所有资源作为静态资源暴露出去
*/
// 注册serviceWorker
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker
      .register('/service-worker.js')
      .then(() => {
        console.log('sw注册成功了~');
      })
      .catch((err) => {
		console.log(err)
        console.log('sw注册失败了~');
      });
  });
}

注意

  1. 只有localhost 和 https 支持sw,注册才有效。因为 Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。

  2. ios部分手机不支持,安卓支持较好。但代理环境下,较新的手机都会注册sw失败,报ssl证书有问题。

  3. 在html上写这么一段代码就能注册sw,其中register第一个参数是指定注册的sw文件,注意sw文件必须在当前页面同级或者更上层。 第二个参数是指定作用的范围,如果浏览器打开过这个html,并且注册这个成功之后,那么scope范围内的其它页面也会受这个sw文件控制了。如果只想当前自己的页面受sw控制,可以直接scope写当前页面的地址。

# webpack之多进程打包

使用thread-loader 进程启动大概为600ms,进程通信也有开销。只有工作消耗时间长,才需要

module: {
    rules: [
      {
          {
            test: /\.js$/,
            exclude: /node_modules/,
            use: [
              {
                loader: 'thread-loader',
                options: {
                  workers: 2 // 进程2个
                }
              },
              {
                loader: 'babel-loader',
                options: {
                  presets: [
                    [
                      '@babel/preset-env',
                      {
                        useBuiltIns: 'usage',
                        corejs: { version: 3 },
                        targets: {
                          chrome: '60',
                          firefox: '50'
                        }
                      }
                    ]
                  ],
                  cacheDirectory: true
                }
              }
            ]
          },

        
      }
    ]
  }
 

# webpack之externals

外部的引入拒绝打包

<!DOCTYPE html>
<html lang="en">
<body>
  <h1 id="title">hello html</h1>
  <script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>
</body>
</html>
module.exports = {
  mode: 'production',
  externals: {
    // 拒绝jQuery被打包进来
    jquery: 'jQuery'
  }
};

# webpack之dll

  • webpack.dll.js
/*
  使用dll技术,对某些库(第三方库:jquery、react、vue...)进行单独打包
    当你运行 webpack 时,默认查找 webpack.config.js 配置文件
    需求:需要运行 webpack.dll.js 文件
      --> webpack --config webpack.dll.js
*/

const { resolve } = require('path');
const webpack = require('webpack');

module.exports = {
  entry: {
    // 最终打包生成的[name] --> jquery
    // ['jquery'] --> 要打包的库是jquery
    jquery: ['jquery'],
	vue:["vue"]
  },
  output: {
    filename: '[name].js',
    path: resolve(__dirname, 'dll'),
    library: '[name]_[hash]' // 打包的库里面向外暴露出去的内容叫什么名字
  },
  plugins: [
    // 打包生成一个 manifest.json --> 提供和jquery映射
    new webpack.DllPlugin({
      name: '[name]_[hash]', // 映射库的暴露的内容名称
      path: resolve(__dirname, 'dll/manifest.json') // 输出文件路径
    }),

  ],
  mode: 'production'
};
  • webpack.config.js AddAssetHtmlWebpackPlugin
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'built.js',
    path: resolve(__dirname, 'build')
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    // 告诉webpack哪些库不参与打包,同时使用时的名称也得变~
    new webpack.DllReferencePlugin({
      manifest: resolve(__dirname, 'dll/manifest.json')
    }),
    // 将某个文件打包输出去,并在html中自动引入该资源
    new AddAssetHtmlWebpackPlugin({
      filepath: resolve(__dirname, 'dll/jquery.js')
    }),
	new AddAssetHtmlWebpackPlugin({
	  filepath: resolve(__dirname, 'dll/vue.js')
	})
  ],
  mode: 'production'
};

import $ from 'jquery';
import vue from "vue"

console.log($);
console.log(vue)
最后更新: 5/1/2023, 9:10:04 AM