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,为当前环境提供一个垫片。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351