webpack学习笔记

  1. 命令窗口运行初始化eslint
    切换到./node_modules/.bin/文件夹下运行 eslint --init
    用airbnb

fix: true可以自动修复问题

// eslint-disable-next-line 不转义

  • 解决eslint不认识浏览器的全局变量如$,修改package.json
"eslintConfig": {
    "extends": "airbnb-base",
    "env": {
        es6: true, // 启用 ES6 语法支持以及新的 ES6 全局变量或类型
        node: true, // Node.js 全局变量和 Node.js 作用域
        browser: true, // 浏览器全局变量
        jquery: true // jQuery 全局变量
    }
}

webpack4.0以上的版本都要安装webpack-cli,否则报错

  1. js兼容

babel @bable/core

  • 基本js兼容性处理 --> @babel/preset-env
    • 问题:只能转换基本语法,如promise高级语法不能兼容
  • 全部js兼容性处理 --> @babel/polyfill
    • 问题:只需要解决部门兼容性问题,但是将所有文件引入体积过大
  • 需要做兼容性处理的就做,按需要加载-->core-js
  1. HMR
  • css文件:style-loader内部实现了
  • js文件:默认不能使用HMR功能-->修改js代码,添加支持HRM功能的代码
if(module.hot) {
    module.hot.accpet('./print.js', function() {
        console.log('Accepting the updated printMe module!');
        printMe();
    })
}

只有print.js会被重新加载,其他文件不会被重新加载
注意:HMR功能对js的处理,只能处理非入口js文件

  • html文件:默认不能使用HMR功能,导致html文件不能热更新(不用HMR功能)
    解决:修改entry入口,将html文件引入

4.source-map
提供了源代码到构造后代码映射关系(如果构建后代码出错了,通过映射可以追踪源代码错误)

  • source-map 外部
    • 错就代码的准备信息和源代码的准确位置
  • inline-source-map 内联
    只生成一个内联的source-map
  • hidden-source-map 外部
    • 错误代码准确信息,没有错误位置,不能追踪代码错误,只能提示到构建后代码的错误位置
  • eval-source-map 内联
    • 每个文件都生成对就的source-map,都在eval
  • nosource-source-map 外部
    • 错误代码准确信息,没有源代码
  • cheap-source-map 外部
    • 错误代码准确信息和源代码的错误位置
    • 只能精确到行,其他的可以精确到列
  • cheap-module-source-map 外部
    • 错误代码准确信息和源代码错误位置
    • module会将loader的source map加入
  • 内联和外部区别:
    1.外部生成了文件,内联没有 2.内联构建速度快
    内联会让代码体积很大,所以生产环境一般不会用内联
  • 速度快:eval>inline>cheap>
    eval-cheap-source-map > eval-source-map
  • 调试更友好
    source-map
    cheap-module-source-map
    cheap-source-map
  • 开发环境:调试友好+速度快 -->eval-source-map(vue,react脚手架用) / eval-cheap-module-source-map
  • 生产环境:
    nosource-source-map 全部隐藏
    hidden-source-map 只隐藏源代码,会提示构建后代码错误信息
devtool: 'source-map' 
  1. 运行npm run dev遇到的问题:Error: Cannot find module 'webpack-cli/bin/config-yargs'
  • 原因:使用webpack到5.*, webpack-cli到4.*,需要使用webpack-cli 3.*
  • 卸载当前的 webpack-cli npm uninstall webpack-cli
  • 安装 webpack-cli 3.* 版本 npm install webpack-cli@3 -D
  1. oneof 只处理一个loader
  • 以免每个loader都执行一遍,提高打包构造速度
  • 如果需要执行多个,可以外部放一个,oneof中放只执行一次
  1. 缓存
  • babel缓存
    • cacheDirectory: true
    • 让第二次打包构建速度更快
  • 文件资源缓存
    • hash: 每次webpack构建时会生成一个唯一的hash值
      • 问题:因为js和css同时使用一个hash值,如果重新打包,会导致所有缓存失效(可我却只改动一个文件)
    • chunkhash: 根据chunk生成hash值,如果打包来源于同一个chunk,那么hash值就一样
      • 问题:js和css的hash值还是一样,因为css是在js中被引入的,所以同属于一个chunk
      • chunk的概念:一个入口文件,
    • contenthash:根据文件的内容生成hash值,不同文件的hash值一定不一样
  1. tree shaking 去除无用代码
  • 前提:1.必须使用ES6模块化 2.开启production环境
  • 作用:减少代码体积
  • 在package.json中配置:sideEffects: false 所有代码都是没有副作用(都可以进行tree shaking)
    • 问题:可能会把css/@babel/polyfill(副作用)文件删掉
    • "sideEffects": ["*.css", "*.less"]表示css文件不用进行tree shaking
  1. 代码分隔
  • 多入口:一个入口,输出一个bundle
entry: {
    index: './src/js/index.js',
    test: './src/js/test.js'
}
  • splitChunks 将node_modules中代码单独打包一个chunk最终输入出
    • 自动分析多入口chunk中,有没有公共的文件,如果有打包成单独一个chunk,不会重复打包多次
optimization: {
    splitChunks: {
        chunks: 'all'
    }
}
  • 通过js代码,让某个文件单独打包成一个chunk
    • import动态导入语法:能将某个文件单独打包
import (/* webpackChunkName: 'test'*/'./test').then(() => {
    console.log('success');
}).catch(() => {
    console.log('error');
})
  1. 懒加载
  • 懒加载:进入页面不会加载print,当点击按钮才会加载print文件
    • 第一次加载后,第二次会直接执行,不会重复加载
    • 使用时才加载
  • webpackPrefetch 预加载
    • 使用之前加载js文件
    • 等其他资源加载完毕,浏览器空闲了,再偷偷加载资源
    • 兼容性不好
  • 正常加载可以认为是并行加载(同时加载多个文件),没有明确的顺序,在前的先加载,同时文件过多过大,影响顺序
button.onclick = () => import(/*webpackChunkName: 'print', webpackPrefetch: true*/'./print').then((module) => {
    const print = module.default;
    print();
});
  1. PWA 渐进式网络开发应用程序(离线可访问)

    workbox --> workbox-webpack-plugin
new workboxWebpackPugin.GenerateSw({
    /*
        1.帮助serviceworker快速启动
        2.删除旧的serviceworker
        生成一个serviceworker配置文件~
    */
    clientClaim: true,
    skipWaiting: true
})

入口文件index.js

/*
  1.eslint不认识window、navigator全局变量
  解决:需要修改package.json中eslintConfig配置
  "env": {
      "browser": true // 支持浏览器端全局变量
  }
  2.serviceworker代码必须运行在服务器上
  --> 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('serviceworker注册成功');
        })
        .catch(() => {
            console.log('注册失败');
        })
    })
}
  1. 多进程打包
{
    test: /\.js$/,
    exclude: /node_modules/,
    use: [
        /* 
            开启多进程打包
            进程启动大概为600ms,进程通信也有开销
            只有工作消耗时间比较长,才需要多进程打包
        */
        {
            loader: 'thread-loader',
            options: {
                worker: 2 // 进程2个
            }
        },
        {
            loader: 'babel-loader',
            options: [
                presets: [
                    [
                        '@babel/preset-env',
                        {
                            useBuiltIns: 'usage',
                            corejs: {version: 3},
                            targets: {
                                chrome: '60',
                                firefox: '50'
                            }
                        }
                    ]
                ]
            ]
        }
    ]
}
  1. externals
externals: {
    // 拒绝打包jquery
    jquery: 'jQuery'
}
  1. dll 优化重复打包第三方库,加快打包速度
  • 使用dll技术,对某些库(第三方库:jquery、react、vue^)进行单独打包

    当你运行webpack时,默认查找webpack.config.js配置文件

    需求:需要运行webpack.dll.js文件

    --> webpack --config webpack.dll.js //webpack配置文件指向webpack.dll.js

  • webpack.dll.js文件

const resolve = require('path');
const webpack = require('webpack');
module.exports = {
    entry: {
        // 最终打包生成的[name] --> jquery
        // ['jquery'] --> 要打包的库是jquery
        jquery: ['jquery']
    },
    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') // 输出文件路径
        })
    ]
}

生成dll文件后,就不需要打包了

  • webpack.config.js文件
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: 'build.js',
        path: resolve(__dirname, 'build')
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: './src/index.html'
        }),
        // 告诉webpack哪些库不参与打包,同时使用时的名称也得变
        new webpack.DllRefrencePlugin({
            manifest: resolve(_dirname,'dll/manifest.json')
        }),
        // 将某个文件打包输出去,并在html中自动引入该资源
        new AddAssetHtmlWebpackPlugin({
            filepath: resolve(__dirname, 'dll/jquery.js')
        })
    ]
}

  1. 性能优化总结
  • webpack性能优化
  • 开发环境性能优化
    • 优化打包构建速度
      • HMR
    • 优化代码调试
      • source-map
  • 生产环境性能优化
    • 优化打包构建事项
      • oneof
      • babel缓存
      • 多进程打包
      • externals
      • dll
    • 优化代码运行的性能
      • 缓存(hash-chunkhash-contenthash)
        • hash:webpack每次打包都会重新生成一个hash
        • chunkhash:同属于一个chunk,共用一个hash
        • contenthash:根据文件内容生成的hash,文件内容变化才会重新生成hash
      • tree shaking
        • es6 module
        • production环境,uglyfy
        • sideEffect参数
      • code split 代码分隔
        • 单入口
        • 多入口
      • 懒加载/预加载
      • pwa
  1. entry
const {resolve} = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

/*
    entry: 入口起点
    1. string --> './src/index.js' 
        单入口
        打包形成一个chunk,输出一个bundle文件
        filename: '[name].js' 此时chunk的名称默认是main
    2. array --> ['./src/index.js', './src/add.js']
        多入口
        所有入口文件最终只会形成一个chunk,输出出去只有一个bundle文件
        --> 只有在HMR功能中让html热更新生效~
    3. object --> {index: './src/index.js', add: './src/add.js'}
        多入口
        有几个入口文件就形成几个chunk,输出几个bundle文件
        此时chunk的名称是key
        --> 特殊用法
        {
            // 所有入口文件最终只会形成一个chunk,输出出去只有一个bundle文件
            index: ['./src/index.js', './src/count.js'], 
            // 形成一个chunk,输出一个bundle文件
            add: './src/add.js'
        }
*/
module.exports = {
    entry: './src/index.js',
    output: {
        filename: '[name].js',
        path: resolve(__dirname, 'build')
    }
}
  1. output
const {resolve} = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: './src/index.js',
    output: {
        // 文件名称(指定名称+目录)
        filename: '[name].js',
        // 输出文件目录(将来所有资源输出的公共目录)
        path: resolve(__dirname, 'build'),
        // 所有资源引入公共路径的前缀 --> 'imgs/a.jpg' --> './imgs/a.jpg'
        publicPath: '/', // 一般用于生产环境
        chunkFilename: '[name]_chunk.js', // 非入口chunk的名称
        // 通常结合dll用
        library: '[name]', // 整个库向外暴露的变量名
        libraryTarget: 'window', // 变量名添加到哪个上 browser
        libraryTarget: 'global', // 变量名添加到哪个上 node
    }
}

index.js

import count from './count';

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

/*
    chunkFilename: '[name]_chunk.js'
    --> 使用:打包成0_chunk.js(0为id)
    --> 不使用:使用filename: '[name].js'规则,打包成0.js
*/
import('./add').then(({default: add}) => {
    console.log(add(1,2);
})

console.log(count(3,2));
  1. loader
const {resolve} = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: './src/index.js',
    output: {
        filename: '[name].js',
        path: resolve(__dirname, 'build'),
    },
    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: {}
            },
            {
                // 以下配置只会生效一个
                oneOf: []
            }
        ],
    }
}
  1. resolve
const {resolve} = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

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

index.js

import '../css/index.css'
--> 可以写成: import '$css/index.css'
  1. devServer
const {resolve} = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

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

浏览器和服务器是存在跨域问题,服务器和服务器不存在跨域问题,浏览器通过代理服务器向服务器发送请求,代理服务器把请求数据返回到代理服务器上

23.optimization

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

module.exports = {
    entry: './src/index.js',
    output: {
        filename: '[name].js',
        path: resolve(__dirname, 'build'),
    },
    module: {
        rules: [
            {
                // loader的配置
                test: /\.css$/,
                // 多个loader用use
                use: ['style-loader', 'css-loader'],
            },
            {
                test: /\.js$/,
                // 排除node_modules下的js文件
                exclude: /node_modules/
            }
        ]
    },
    mode: 'production',
    optimization: {
        splitChunks: {
            chunks: 'all',
            // 以下是默认值,可以不写
            /*
            minSize: 30*1024, // 分割的chunk最小为30kb
            maxSize: 0, // 最大没有限制
            minChunks: 1, // 要提取的chunk最少被引用1次
            maxAsyncRequests: 5, // 按需加载时并行加载的文件的最大数量
            maxInitialRequests: 3, // 入中js文件最大并行请求数量
            automaticNameDelimiter: '~', // 名称连接符 vendors~xxx.js 因为这个里是~
            name: true, //可以使用命名规则
            cacheGroups: { // 分割chunk的组
                // node_modules文件会被打包到vendors --> vendors~xxx.js xxx是模块名称
                // 满足上面的公共规则:如:大小超过30kb,至少被引用一次
                vendors: { // vendors~xxx.js 因为这个里是vendors
                    test: /[\\/]node_modules[\\/]/,
                    // 打包的优先级
                    priority: -10
                },
                default: {
                    // 要提取的chunk最少被引用2次
                    minChunks: 2,
                    // 优先级
                    priority: -20,
                    // 如果当前要打包的模块,和之前已经被提取的模块是同一个,就会复用,而不是重新打包模块
                    reuseExistingChunk: true
                }
            }
            */
        },
        // 将当前模块的记录其他模块的的hash单独打包为一个文件runtime
        // 解决:修改a文件导致b文件contenthash变化
        runtimeChunk: {
            name: entrypoint => `runtime-${entrypoint.name}`
        },
        minimizer: {
            // 配置生产环境的压缩方案:js和css
            new TerserWebpackPlugin: {
                // 开启缓存
                cache: true,
                // 开启多进程打包
                parallel: true,
                // 启动source-map
                sourceMap: true
            }
        }
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容