webpack 学习笔记

常用配置

webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin') // 提供 html 模板
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer') // 查看打包后模块依赖关系
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin') // 提高打包构建速度 第一次缓存 第二次速度提升
const OptimizeCssWebpackPlugin = require('optimize-css-assets-webpack-plugin') // 压缩css
// 去掉多余 css
const PurgeCSSPlugin = require('purgecss-webpack-plugin')
const glob = require('glob')
// 查看打包
const speedMeasureWebpackPlugin = require('speed-measure-webpack-plugin') // 记录打包各个模块时间
const smw = new speedMeasureWebpackPlugin()
smp.wrap({/*包裹整个配置对象*/})

const MiniCssExtractPlugin = require('mini-css-extract-plugin') // 提取 css 为单独文件
const {CleanWebpackPlugin}  = require('clean-webpack-plugin') // 每次打包 清理 dist 目录
const HappyPack = require('happypack')
const webpack = require('webpack')
module.exports = {
  mode: 'development',// development
  entry: './src/index.js', // 每个 entry 产出一个 chunk
  // entry: {  // 多页面打包
  //   index: './src/index.js',
  //   cart: './src/cart.js'
  // },
},
  output: {
    // publicPath: 'https://www.baidu.com', // 也可以单独给某个资源添加前缀
    filename: '[name].[hash:8].js',
    path: path.resolve('./dist')
  },
  devServer: {
    // 运行代码的目录 
    contentBase: path.resolve(__dirname, './dist'),
    // 监视 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,
    before(app) { // 可不用代理提供响应内容
      app.get('/api/user', (req, res) => {
        res.send({a: 1})
      })
    }
    // 服务器代理 --> 解决开发环境跨域问题 
    proxy: {
      // 一旦 devServer(5000)服务器接受到 /api/xxx 的请求,就会把请求转发到另外一个服务器 (3000)
      '/api': {
        target: 'http://localhost:3000',
        // 发送请求时,请求路径重写:将 /api/xxx --> /xxx (去掉/api) 
        pathRewrite: { '^/api': '' }
      }
    }
  },
  // 解析模块的规则 
  resolve: {
    // 配置解析模块路径别名: 优点简写路径 缺点路径没有提示 
    alias: { '@': path.resolve('src'), },
    // 配置省略文件路径的后缀名 
    extensions: ['.js', '.json', '.jsx', '.css'], 
    // 告诉 webpack 解析模块是去找哪个目录 
    modules: ['./node_modules']
  },
  module: {
    noParse: [/jquery/], // 不去解析 jquery 中的依赖库
    rules: [ 
      // {
      //   test: require.resolve('jquery'),
      //   loader: 'expose-loader', // 把 变量 暴露到全局
      //   options: {
      //     exposes: ['$', 'jQuery'],
      //   },
      // },
      {
        test: /\.html$/, // 解析 html 中的图片
        use: 'html-loader'
      },
      {
        test: /\.(png|gif|jpg)$/,
        use: {
          loader: 'url-loader',
          options: {
            // 图片大小小于 8kb,就会被 base64 处理 // 优点: 减少请求数量(减轻服务器压力) // 缺点:图片体积会更大(文件请求速度更慢) 
            limit: 8 * 1024,
            esModule: false, // 问题:因为 url-loader 默认使用 es6 模块化解析,而 html-loader 引入图片是 commonjs // 解析时会出问题:[object Module] // 解决:关闭 url-loader 的 es6 模块化,使用 commonjs 解析  
            name: '[hash:10].[ext]', // 给图片进行重命名 [hash:10]取图片的 hash 的前 10 位 [ext]取文件原来扩展名 
            outputPath: '/image/',
            publicPath: 'https://www.baidu.com' // 给图片加上公共路径
          }
        },

      },
      {
        test: /\.css$/,
        use: [{
          loader: 'style-loader',
          options: {
            insert: 'top' // 插入到 html style 的下面
          }
        }, 'css-loader']
      },
      {
        test: /\.less$/,
        use: [{
          loader: MiniCssExtractPlugin.loader, // 可抽离出来复用到 css 配置项
          options: {
            insert: 'top' // 插入到 html style 的下面
          }
        }, 'css-loader',
        {
          loader: 'postcss-loader',
          options: {
            ident: 'postcss',
            plugins: () => {
              require('postcss-preset-env')()
            }
          }
        },
          'less-loader']
      },
      {
        test: /\.js$/,
        include: path.resolve(__dirname, './src'),
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: { // 用 babel-loader 需要把 es6 转换成 es5
            // 插件集合 只会转换语法 箭头函数, const
            // presets: ['@babel/preset-env', ],
            presets: [['@babel/preset-env', {
              // 按需加载
              useBuiltIns: 'usage',
              // 制定 core-js的版本
              corejs: {
                version: 3
              },
              // 指定兼容性做到哪个版本的浏览器
              targets: {
                chrome: '60',
                firefox: '60',
                ie: '9',
                safari: '10',
                edge: '17'
              }
            }], ['@babel/react']], // react 预设
            plugins: [
              ['@babel/plugin-proposal-decorators', { 'legacy': true }] // 配置装饰器
            ]
          }
        }
      },
      // {
      //   test: /\.js$/,
      //   exclude: /node_modules/,
      //   use: [
      //     {
      //       loader: 'eslint-loader',
      //       options: {
      //       enforce: 'pre', // previous 之前, post 之后, 默认 loader 从下到上从右到左执行
      //       fix: true // 自动修复
      //       }
      //     }
      //   ]
      // }
    ]
  },
  opimization: {
    usedExports: true, // tree shaking 生产模式 默认开启
    splitChunks: {
      chunks: 'all',// 支持同步引入模块也支持异步
      minChunks: 1 // 最少1个 chunk引入了该模块    
    }
  },
  plugins: [
  new HardSourceWebpackPlugin(),
   new BundleAnalyzerPlugin(),
    new PurgeCSSPlugin({ // 摇树 css
      paths: glob.sync(`./src/**/*`,  { nodir: true }),
    }),
    new webpack.hotModuleReplacementPlugin() // 开启热更新  需要 指定热更新位置
    // if (module.hot) { // js 文件中 使用
    //   module.hot.accept('./main.js', () => {
    //     require('./main.js')
    //     //  TODO
    //   })
    // }
    // if (module.hot) {
    //   module.hot.accept()
    // }
    new webpack.IgnorePlugin(/\.\/locale/, /moment/),  // 忽略引入内容
    new webpack.DefinePlugin({ // 定义全局变量
      'process.env.NODE_ENV': JSON.stringify('production')
    }),
    new webpack.BannerPlugin('by Misdirection'), // 定义版权
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      // chunk: ['index'] // 多页面打包 配置多个 HtmlWebpackPlugin 引入 entry 的 index 选项的 js 文件 
      template: './src/index.html',
      filename: 'index.html',
      minify: {
        removeAttributeQuotes: true, // 删除双引号
        collapseWhitespace: true // 删除空格
      },
       // chunksSortMode: 'manual' // 按照 chunks 的顺序 排序引入页面
      hash: true
    }),
    new MiniCssExtractPlugin({
      filename: 'css/build.[hash:8].css'
    }),
    new OptimizeCssWebpackPlugin(), // 生产环境下才会压缩
    // new webpack.ProvidePlugin({ // 把 jquery 注入到每个模块
    //   $: 'jquery'
    // })
  ],
  // 构建单吗后监听文件是否变化重新打包
  // watch: true,
  // watchOptions: {
  //   pool: 1000, // 1s 询问一次
  //   aggregateTimeout: 1000 // 用来防抖
  // },
  externals: {
    jquery: '$'
  }
  devtool: 'source-map'
}

source-map

通过 sourceMappingURL=bundle.js.map 映射源文件


source-map.png

项目依赖

{
  "name": "new-webpack",
  "version": "1.0.0",
  "description": "",
  "main": "webpack.config.js",
  "scripts": {
    "dev": "webpack-dev-server --config ./webpack.config.js",
    "build": "webpack"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.10.4",
    "@babel/plugin-proposal-decorators": "^7.10.4",
    "@babel/polyfill": "^7.10.4",
    "@babel/preset-env": "^7.10.4",
    "@babel/preset-react": "^7.10.4",
    "babel-loader": "^8.1.0",
    "clean-webpack-plugin": "^3.0.0",
    "core-js": "^3.6.5",
    "css-loader": "^3.6.0",
    "eslint": "^7.4.0",
    "eslint-loader": "^4.0.2",
    "expose-loader": "^1.0.0",
    "file-loader": "^6.0.0",
    "happypack": "^5.0.1",
    "html-loader": "^1.1.0",
    "html-webpack-plugin": "^4.3.0",
    "less-loader": "^6.2.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.2.1",
    "url-loader": "^4.1.0",
    "webpack": "^4.43.0",
    "webpack-cli": "^3.3.12",
    "webpack-dev-server": "^3.11.0",
    "webpack-merge": "^5.0.9"
  },
  "browserslist": {
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ],
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ]
  },
  "dependencies": {
    "@babel/polyfill": "^7.10.4",
    "core-js": "^3.6.5",
    "jquery": "^3.5.1",
    "react": "^16.13.1",
    "react-dom": "^16.13.1"
  }
}

环境分离

webpack.prod.js

const base = require('./webpack.base')
const { merge } = require('webpack-merge')
module.exports = merge(base, {
  mode: 'production'
})

webpack.dev.js

const base = require('./webpack.base')
const { merge } = require('webpack-merge')
module.exports = merge(base, {
  mode: 'development'
})

webpack.base.js

const {resolve} = require('path')
module.exports  ={
  entry: './src/index.js',
  output: {
    filename: 'index.js',
    path: resolve(__dirname, './dist') 
  }
}

优化配置

module: {
  noParse: [/jquery/], // 不去解析 jquery 中的依赖库
  {
    test: /\.js$/,
    exclude: /node_modules/ // 不打包 node_modules 中的文件
  }
},
externals: {
    jquery: '$' // 不去打包 jquery, 需要自己 cdn 引入资源
},
plugins: [
   new webpack.IgnorePlugin(/\.\/locale/, /moment/),  // 忽略引入的 locale
]

动态连接库

webpack.config.react.js

加快打包速度,将不会变的第三方模块先进行打包,通过 webpackReferencePlugin 引用

const path = require('path')
const webpack = require('webpack')
module.exports = {
  mode: 'development',
  entry: {
    // react 为生成的文件 name 
    react: ['react', 'react-dom']
  },
  output: {
    filename: '_dll_[name].js',
    path: path.resolve(__dirname, './dist'),
    library: '_dll_[name]',
    libraryTarget: 'var' // commonjs umd
  },
  plugins: [
    new webpack.DllPlugin({ // name = libraryname
      name: '_dll_[name]', // 和 library 名字一致
      path: path.resolve(__dirname, './dist', 'manifest.json')
    })
  ]
}

webpack.config.js

plugins: [
  new webpack.DllReferencePlugin({
      manifest: path.resolve(__dirname, 'dist', 'manifest.json')
  }),
]

hanppypack

多线程打包

  module: {
    rules: [
       {
        test: /\.css$/,
        use: 'HappyPack/loader?id=css'
       },
    ]
  }
  plugins: [
    new HappyPack({
      id: 'css',
      use: [{
        loader: 'style-loader',
        options: {
          insert: 'top' // 插入到 html style 的下面
        }
      }, 'css-loader'
      ]
    })
]

懒加载

import React from 'react'
import ReactDom from 'react-dom'
function App () {
  function click() {
    import('./other').then(res => {
      console.log(res.default)
    })
  }
  return (
    <div>
      <button onClick={click}>点击</button>
    </div>
  )
}
ReactDom.render(<App/>, document.querySelector('#root'))

环境配置

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