你知道的webpage

237 阅读7分钟

1.webpack 是什么

前端的构建工具,负责根据各个模块的关系对各种文件的打包、解析、优化等。

image.png

2.接下来让我们看看它的五个核心

1.entry

这个是webpage的入口,规定webpage从哪个文件开始打包。
module.exports = {
  entry: {
    index: ['./src/index.js', './src/count.js'], 
    add: './src/add.js'
  },
  ...
};
/*
  entry: 入口起点
    1. string --> './src/index.js'
      单入口
      打包形成一个chunk。 输出一个bundle文件。
      此时chunk的名称默认是 main
    2. array  --> ['./src/index.js', './src/add.js']
      多入口
      所有入口文件最终只会形成一个chunk, 输出出去只有一个bundle文件。
        --> 只有在HMR功能中让html热更新生效~
    3. object
      多入口
      有几个入口文件就形成几个chunk,输出几个bundle文件
      此时chunk的名称是 key

      --> 特殊用法
        {
          // 所有入口文件最终只会形成一个chunk, 输出出去只有一个bundle文件。
          index: ['./src/index.js', './src/count.js'], 
          // 形成一个chunk,输出一个bundle文件。
          add: './src/add.js'
        }
*/

2.output

这是webpage打包之后文件的出口,规定输出的位置和命名。
const {resolve} = require('path')
module.exports = {
  output: {
    // 文件名称(指定名称+目录)
    filename: 'js/[name].js',
    // 输出文件目录(将来所有资源输出的公共目录)
    path: resolve(__dirname, 'build'),
    // 所有资源引入公共路径前缀 --> 'imgs/a.jpg' --> '/imgs/a.jpg'
    publicPath: '/',
    chunkFilename: 'js/[name]_chunk.js', // 非入口chunk的名称
    // library: '[name]', // 整个库向外暴露的变量名
    // libraryTarget: 'window' // 变量名添加到哪个上 browser
    // libraryTarget: 'global' // 变量名添加到哪个上 node
    // libraryTarget: 'commonjs'
  }
};

3.loader

让webpage能够解析除了js的其他文件。!!!use的解析顺序是从右向左的
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  ...
  module: {
    rules: [
      // loader的配置
      {
        test: /\.css$/,
        // 多个loader用use
        use: ['style-loader', 'css-loader']
      },
      {
        test: /\.js$/,
        // 排除node_modules下的js文件
        exclude: /node_modules/,
        // 只检查 src 下的js文件
        include: resolve(__dirname, 'src'),
        // 优先执行
        enforce: 'pre',
        // 延后执行
        // enforce: 'post',
        // 单个loader用loader
        loader: 'eslint-loader',
        options: {}
      },
      {
        // 以下配置只会生效一个,避免所有的文件都判断一遍浪费时间,如果处理的是同一类型文件,那么就要在外层添加loader分开配置
        oneOf: []
      }
    ]
  },
  ...
};

4.plugins

功能比loader更加强大,可以进行代码优化、压缩、环境配置等等。
plugins: [
    new MiniCssExtractPlugin({
      filename: 'css/built.css'// 对输出的css文件进行重命名
    }),
    new OptimizeCssAssetsWebpackPlugin(),
    new HtmlWebpackPlugin({//HTML加载解析
      template: './src/index.html',
      minify: {
        collapseWhitespace: true,//删除空格
        removeComments: true//移除注释
      }
    })
  ],

5.mode

    设置运行的环境,有开发环境(development)和生产环境(production)之分。
    
module.exports = {
  ...
  mode: 'development'
  //mode:'production'
};

3.其他重点

3.1.优化环境配置

针对不同的环境,对性能优化有不同的要求:
针对开发环境:①打包速度要快;②代码要方便调试;
针对生产环境:①打包速度要快;②运行的代码性能要好;

HMR

  HMR: hot module replacement 热模块替换 / 模块热替换
  
  作用:一个模块发生变化,只会重新打包这一个模块(而不是打包所有模块) 
       极大提升构建速度
  
  样式文件:可以使用HMR功能:因为style-loader内部实现了
  
  js文件:默认不能使用HMR功能 --> 需要修改js代码,添加支持HMR功能的代码
  
    注意:HMR功能对js的处理,只能处理非入口js文件的其他文件。
    
  html文件: 默认不能使用HMR功能.同时会导致问题:html文件不能热更新了~ (不用做HMR功能)
    解决:修改entry入口,将html文件引入(多入口entry设置)
    
module.exports = {
  ...
  devServer: {//开启本地服务器,不用每次都手动build运行
    contentBase: resolve(__dirname, 'build'),
    compress: true,
    port: 3000,
    open: true,
    // 开启HMR功能
    // 当修改了webpack配置,新配置要想生效,必须重新webpack服务
    hot: true
  }
  ...
};

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'
  ...
};

oneOf

正常来说一个文件只会匹配一个loader,如果每次都从头开始匹配,那么会浪费很多性能,所以设置oneOf就只有一个会被匹配,可是如果有一个文件要两个loader的呢,那就把其中一个提取出来,这样就可以匹配两个了。
const commonCssLoader = [
  MiniCssExtractPlugin.loader,
  'css-loader',
  {
    // 还需要在package.json中定义browserslist
    loader: 'postcss-loader',
    options: {
      ident: 'postcss',
      plugins: () => [require('postcss-preset-env')()]
    }
  }
];

module.exports = {
  ...
  module: {
    rules: [
      {//单独提取出来,因为js要经过两个loader的处理
        // 在package.json中eslintConfig --> airbnb
        test: /\.js$/,
        exclude: /node_modules/,
        // 优先执行
        enforce: 'pre',
        loader: 'eslint-loader',
        options: {
          fix: true
        }
      },
      {
        // 以下loader只会匹配一个
        // 注意:不能有两个配置处理同一种类型文件
        oneOf: [
          {
            test: /\.css$/,
            use: [...commonCssLoader]
          },
          {
            test: /\.less$/,
            use: [...commonCssLoader, 'less-loader']
          },
          /*
            正常来讲,一个文件只能被一个loader处理。
            当一个文件要被多个loader处理,那么一定要指定loader执行的先后顺序:
              先执行eslint 在执行babel
          */
          {
            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'
            }
          }
        ]
      }
    ]
  },
  ...
};

缓存

 缓存:
babel缓存
  cacheDirectory: true
  --> 让第二次打包构建速度更快
文件资源缓存
  hash: 每次wepack构建时会生成一个唯一的hash值。
    问题: 因为js和css同时使用一个hash值。
      如果重新打包,会导致所有缓存失效。(可能我却只改动一个文件)
  chunkhash:根据chunk生成的hash值。如果打包来源于同一个chunk,那么hash值就一样
    问题: js和css的hash值还是一样的
      因为css是在js中被引入的,所以同属于一个chunk
  contenthash: 根据文件的内容生成hash值。不同文件hash值一定不一样    
  --> 让代码上线运行缓存更好使用
 
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"]

code split

对代码进行分个打包

lazy loading

在模块被调用时,才进行加载,区别预加载:在浏览器空闲的时候进行加载(兼容性差)。
  // 懒加载:当文件需要使用时才加载
  // 预加载 prefetch:会在使用之前,提前加载js文件 
  // 正常加载可以认为是并行加载(同一时间加载多个文件)  
  // 预加载 prefetch:等其他资源加载完毕,浏览器空闲了,再偷偷加载资源
  import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test').then(({ mul }) => {
    console.log(mul(4, 5));
  });

pwa

 PWA: 渐进式网络开发应用程序(离线可访问),让网站在离线的情况下还能正常显示。

多进程打包

多个进程对文件进行打包,但是开启进程也要一定的性能消耗,所以在多文件的时候使用

externals

由于有些文件可能不需要打包在后续通过cdn的形式进行引入,所以可以配置其不被打包。

dll

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

3.2 解析模块的规则

设置模块的一些解析规则
module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/[name].js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  },
  plugins: [new HtmlWebpackPlugin()],
  mode: 'development',
  // 解析模块的规则
  resolve: {
    // 配置解析模块路径别名: 优点简写路径 缺点路径没有提示
    alias: {
      $css: resolve(__dirname, 'src/css')
    },
    // 配置省略文件路径的后缀名
    extensions: ['.js', '.json', '.jsx', '.css'],
    // 告诉 webpack 解析模块是去找哪个目录
    modules: [resolve(__dirname, '../../node_modules'), 'node_modules']
  }
};

3.3 开发时服务器

设置开发时候的服务器,端口、域名、跨域处理等
module.exports = {
  ...
  devServer: {
    // 运行代码的目录
    contentBase: resolve(__dirname, 'build'),
    // 监视 contentBase 目录下的所有文件,一旦文件变化就会 reload
    watchContentBase: true,
    watchOptions: {
      // 忽略文件
      ignored: /node_modules/
    },
    // 启动gzip压缩
    compress: true,
    // 端口号
    port: 5000,
    // 域名
    host: 'localhost',
    // 自动打开浏览器
    open: true,
    // 开启HMR功能
    hot: true,
    // 不要显示启动服务器日志信息
    clientLogLevel: 'none',
    // 除了一些基本启动信息以外,其他内容都不要显示
    quiet: true,
    // 如果出错了,不要全屏提示~
    overlay: false,
    // 服务器代理 --> 解决开发环境跨域问题
    proxy: {
      // 一旦devServer(5000)服务器接受到 /api/xxx 的请求,就会把请求转发到另外一个服务器(3000)
      '/api': {
        target: 'http://localhost:3000',
        // 发送请求时,请求路径重写:将 /api/xxx --> /xxx (去掉/api)
        pathRewrite: {
          '^/api': ''
        }
      }
    }
  }
};