webpack打包详解(页面性能优化)

webpack打包详解(页面性能优化)

先附上webpack的中文文档地址:https://www.webpackjs.com/

先让我们来了解一下什么是webpack

webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle,在webpack眼里一切都可以打包,打包成js文件

一.下载webpack环境

npm i webpack@4 webpack-cli@3 -D
安装webpack4 配置 cli3

接下来做一个小测试
在src下创建app.js
src/app.js

import name from './name.js'
console.log(name)

src/name.js

export default 'test'

命令行输入打包指令

 webpack --entry ./src/app.js --output dist/bundle.js
//--entry后为入口文件地址 --output后为出口文件地址
// 可以进入dist文件夹中查看是否打包成功

二.正式配置

第一步运行完毕,成功打包,说明了webpack的环境已经安装成功,接下来就可以开始正式的打包配置了

2.1 入口及出口的配置

src/app.js

console.log(config)
//app.js 入口文件中打印一个字符

根目录下创建config/webpack.config.dev.js

const path = require('path')
module.exports = {
    mode: 'development',
   //入口,从入口开始逐个模块打包
  entry: {
    app: path.resolve(__dirname, '../src/app.js')
  },
 //出口,编译完成输出文件夹
  output: {
    path: path.resolve(__dirname, '../dist'),
    filename: '[name].js' //[name] 拿到entry中的key值
  }
}

配置启动命令

  "scripts": {
    "dev": "",
    "build": "webpack --config ./config/webpack.config.dev.js"
  }
2.2. 载入其他功能插件

npm i html-webpack-plugin -D/ yarn add html-webpack-plugin -D 解析html
npm i copy-webpack-plugin -D/ yarn add copy-webpack-plugin -D打包小图标
npm i clean-webpack-plugin -D/ yarn add clean-webpack-plugin -D
删除dist中的文件

解析html:导入html-webpack-plugin

const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
    ...,
 //插件
  plugins: [
    new HtmlWebpackPlugin({
   //导入文件名
      filename: 'index.html',
    //模板:导入的html路径
      template: path.resolve(__dirname, '../public/index.html')
    })
  ]
}

打包小图标:导入copy-webpack-plugin

const CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = {
    ...,
  plugins: [
    ...,
    new CopyWebpackPlugin({
        patterns: [
        {
          from: resolve('public/favicon.ico'),
          to: resolve('dist/')
        }
      ]
        })
  ]
}

删除dist图片:npm i clean-webpack-plugin

const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
    ...,
  plugins: [
    new CleanWebpackPlugin (),
    ...
  ]
}
2.3. 样式处理

打包css也是css代码压缩,简单来说就是无效代码删除和css语义合并css的样式文件可分为css、stylus、scss,打包不同类型的文件,也需要下载对应的模块
css:npm i css-loader -D / yarn add css-loader -D
stylus:npm i stylus stylus-loader -D / yarn add stylus stylus-loader -D
scss:npm i node-sass sass-loader -D / yarn add ndoe-sass sass-loader -D
style-loader:npm i style-loader -D / yarn add style-loader -D
css抽离:npm i mini-css-extract-plugin -D / yarn add mini-css-extract-plugin -D

2.3.1. css:css类型文件的打包

module.exports = {
  ...,
//模块
  module: {
    rules: [
        ...,
      {
        //正则验证,.css为后缀的使用css-loader
        test: /\.css$/,
        //载入程序:
        loader: 'css-loader'
      }
    ]
  },
  ...
}

若样式导入进js中,将其解析出来需要使用style-loader 模块
npm i style-loader -D / yarn add style-loader -D

module.exports = {
  ...,
  module: {
    rules: [
        ...,
      {
        test: /\.css$/,
        // loader: 'css-loader'
         loaders: ['style-loader', 'css-loader'] // 后面的模块为前面的服务
      }
    ]
  },
  ...
}

2.3.2. stylus:stylus类型的打包

module.exports = {
  ...,
  module: {
    rules: [
        ...,
      {
        test: /\.(css|styl)$/,
        // loader: 'css-loader'
        loaders: ['style-loader', 'css-loader', 'stylus-loader'] // 后面的为前面的服务
      }
    ]
  },
  ...
}

2.3.3. scss:scss类型的打包

module.exports = {
  ...,
  module: {
    rules: [
        ...,
      {
        test: /\.scss$/,
        loaders: ['style-loader', 'css-loader', 'sass-loader'] // 后面的为前面的服务
      }
    ]
  },
  ...
}

2.3.4. css的抽离:css代码被css-loader转换后,交给的是style-loader进行处理。

style-loader使用的方式是用一段js代码,将样式加入到style元素中。而实际的开发中,我们往往希望依赖的样式最终形成一个css文件

此时,就需要用到一个库:mini-css-extract-plugin,该库提供了1个plugin和1个loader

const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
    ...,
  module: {
    rules: [
      ...,
      {
        test: /\.(css|styl)$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'stylus-loader']
      },
      {
        test: /\.scss$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']
      }
    ]
  },
  plugins: [
    ...,
    new MiniCssExtractPlugin({
      filename: 'style/[name].css'
    })
  ],
  ...
}

2.3.5. 自动补全css
对于css属性,尤其对css3的新属性而言,浏览器私有属性前缀,是非常滴重要,然鹅,如果要知道每个属性的浏览器私有前缀,那是非常的麻烦,“如何补齐css前缀”,且看**autoprefixer

//配置文件中
const loaderUse = (fileLoader) => {
  return [
    {
      loader: MiniCssExtractPlugin.loader,
      options: {
        publicPath: '../'
      }
    },
    'css-loader',
    'postcss-loader', // ******
    fileLoader
  ]
} 

//posts.config.js
module.exports = {
  plugins: [
    require('autoprefixer')({
      overrideBrowserslist: ['last 100 versions']
    })
  ]
}
//如果不设置配置文件
const loaderUse = (fileLoader) => {
  return [
    {
      loader: MiniCssExtractPlugin.loader,
      options: {
        publicPath: '../'
      }
    },
    'css-loader',
    {
      loader: 'postcss-loader',
      options: {
        postcssOptions: {
          plugins: [
            [
              'autoprefixer',
              {
                overrideBrowserslist: ['last 100 versions']
              }
            ],
            [
              'postcss-preset-env',
              {
                // Options
              }
            ]
          ]
        }
      }
    },
    fileLoader
  ]
} 
2.4. 处理图片

方式一:src/assets图片

module.exports = {
    ...,
  module: {
    rules: [
      ...,
      {
        test: /\.(jpg|png|jpeg|gif)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 2048 // 如果设置的值够大,显示base64(内存中),如果设置的值小,显示图片地址
            }
          }
        ]
      }
    ]
  },
  ...
}

如果需要设置图片的输出目录

module.exports = {
    ...,
  module: {
    rules: [
      ...,
      {
        test: /\.(jpg|png|jpeg|gif)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 2048, // 如果设置的值够大,显示base64,如果设置的值小,显示图片地址
              outputPath: '../dist/images' // 不要写绝对路径 类似 path.resolve()ß
            }
          }
        ]
      }
    ]
  },
  ...
}

方式二:放入public 拷贝至 dist

不受webpack管辖,直接拷贝即可,使用copy-webpack-plugin插件

module.exports = {
    ...,
  plugins: [
    ...,
    new CopyWebpackPlugin({
      patterns: [
        {
          context: resolve('public/'), // 一定要添加上下文对象,否则直接复制public目录至dist
          from: '**/*',
          to: resolve('dist/'),
          globOptions: {
            ignore: ['index.html']
          }
        }
      ]
    }),
        ...
  ],
  ...
}
2.5. 处理高级js

npm i @babel/core @babel/preset-env babel-loader -D / yarn add @babel/core @babel/preset-env babel-loader -D
@babel/preset-env是一系列插件的集合,包含了我们在babel6中常用的es2015,es2016, es2017等最新的语法转化插件,允许我们使用最新的js语法,比如 let,const,箭头函数等等,但不包括stage-x阶段的插件。

module.exports = {
    ...,
  module: {
    rules: [
      {
        test: /\.js$/,
        // loader: 'babel-loader '
        // bower_components 是以前的一种包管理器 bootstrap
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      },
      ...
    ]
  },
  ...
}

但是如果给类添加属性之后,就会发生错误,此时需要添加新的插件

npm i @babel/plugin-proposal-class-properties -D / yarn add @babel/plugin-proposal-class-properties -D

module.exports = {
    ...,
  module: {
    rules: [
      {
        test: /\.js$/,
        // loader: 'babel-loader '
        // bower_components 是以前的一种包管理器 bootstrap
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'],
            plugins: ['@babel/plugin-proposal-class-properties']
          }
        }
      },
      ...
    ]
  },
  ...
}

如果代码包含 async await,需要添加新的插件

npm i @babel/plugin-transform-runtime @babel/runtime -D / yarn add @babel/plugin-transform-runtime @babel/runtime -D

module.exports = {
    ...,
  module: {
    rules: [
      {
        test: /\.js$/,
        // loader: 'babel-loader '
        // bower_components 是以前的一种包管理器 bootstrap
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'],
            plugins: [
              '@babel/plugin-proposal-class-properties',
              '@babel/plugin-transform-runtime'
            ]
          }
        }
      },
      ...
    ]
  },
  ...
}

如果代码中有装饰器,同样需要添加新的插件

npm i @babel/plugin-proposal-decorators -D / yarn add @babel/plugin-proposal-decorators -D

module.exports = {
    ...,
  module: {
    rules: [
      {
        test: /\.js$/,
        // loader: 'babel-loader '
        // bower_components 是以前的一种包管理器 bootstrap
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'],
            plugins: [
              '@babel/plugin-transform-runtime', // 处理async await
              [
                '@babel/plugin-proposal-decorators',
                {
                  legacy: true
                }
              ],
              [
                '@babel/plugin-proposal-class-properties',  // 处理类的属性
                {
                  loose: true
                }
              ]
            ] 
          }
        }
      },
      ...
    ]
  },
  ...
}

如果js中含有浏览器解析不了的语句,可以使用垫片

npm i @babel/polyfill -D / yarn add @babel/polyfill -D

方式一:入口文件顶级添加如下语句

import "@babel/polyfill";

方式二:配置文件入口处

module.exports = {
  entry: {
    app: ["@babel/polyfill", path.resolve(__dirname, '../src/app.js')]
  }
}

方式三:添加@babel/preset-env时添加配置选项

module.exports = {
    ...,
  module: {
    rules: [
      {
        test: /\.js$/,
        // loader: 'babel-loader '
        // bower_components 是以前的一种包管理器 bootstrap
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [
              [
                '@babel/preset-env',
                {
                  useBuiltIns: 'usage'
                }
              ]
            ],
            plugins: [
              '@babel/plugin-transform-runtime', // 处理async await
              [
                '@babel/plugin-proposal-decorators',
                {
                  legacy: true
                }
              ],
              [
                '@babel/plugin-proposal-class-properties',  // 处理类的属性
                {
                  loose: true
                }
              ]
            ] 
          }
        }
      },
      ...
    ]
  },
  ...
}

此时运行查看会有提示信息安装core-js模块,需要安装配置

npm i core-js@3 -D / yarn add core-js@3 -D

module.exports = {
    ...,
  module: {
    rules: [
      {
        test: /\.js$/,
        // loader: 'babel-loader '
        // bower_components 是以前的一种包管理器 bootstrap
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [
              [
                '@babel/preset-env',
                {
                  useBuiltIns: 'usage',
                  corejs:3
                }
              ]
            ],
            plugins: [
              '@babel/plugin-transform-runtime', // 处理async await
              [
                '@babel/plugin-proposal-decorators',
                {
                  legacy: true
                }
              ],
              [
                '@babel/plugin-proposal-class-properties',  // 处理类的属性
                {
                  loose: true
                }
              ]
            ] 
          }
        }
      },
      ...
    ]
  },
  ...
}

为什么要使用垫片

Babel默认只转换新的JavaScript句法(syntax),而不转换新的API,比如 Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全局对象,以及一些定义在全局对象上的方法(比如Object.assign)都不会转码。举个例子,ES6在Array对象上新增了Array.from方法。Babel就不会转码这个方法。如果想让这个方法运行,必须使用babel-polyfill,为当前环境提供一个垫片。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。