从零开始学webpack「v5.0」

webpack
webpack是一个现代JavaScript应用程序的静态模块打包工具。当webpack处理应用程序时,它会在内部构建一个依赖图(dependency [/dɪˈpendənsi/] graph [/ɡræf/]),此依赖图会映射项目所需的每个模块,并生成一个或多个bundle /ˈbʌndl/ 包!

为啥要学webpack?

之前我们在讲前端性能优化的时候,我们可以从以下几个方面,去提高页面第一次渲染的速度,以及运行时的性能:

 

/* 
第一类:明显改善页面第一次加载的速度,减少白屏等待的时间
    @1 使用骨架屏技术
     + 服务器骨架屏:页面首屏内容是基于服务器渲染的
     + 前端骨架屏:在真实内容渲染出来之前,我们给予相关位置用“色框”占位!
    @2 减少HTTP请求的次数和大小
     + CSS/JS都要做合并压缩「尽可能合并为一个CSS和一个JS」
     + 图片的合并压缩,例如:雪碧图(CSS Sprit)
     + 使用图片BASE64来加快图片的渲染!
     + 图片一定要做延迟加载
     + 使用字体图片和矢量图,来代替位图!!
     + 音视频资源也要设置延迟加载「preload='none'」
     + 数据的异步加载
     + 开启服务器端的GZIP压缩,可以让访问的资源压缩60%+
    @3 渲染页面中的优化
     + 因为<script>会阻碍GUI渲染,所以:把其放在页面末尾,或者设置async/defer!
     + 因为<link>是异步加载CSS资源,所以建议放在页面开始,尽快获取样式资源!!
     + 不要使用import导入CSS,因为其会阻碍GUI的渲染!
     + 样式代码较少的情况下,使用<style>内嵌式,尤其是移动端开发!
    @4 在网络传输中的优化
     + 减少cookie的内容大小「因为只要向服务器发送请求,不论服务器是否需要,总会在请求头中把cookie传递给服务器,如果cookie比较大,会让所有的请求都变慢」
     + 资源分服务器部署「例如:web服务器、图片服务器、数据服务器」,这样可以降低服务器压力,提高服务器的并发,让资源能更加合理的被利用;但是也会导致DNS解析次数增加,此时可以基于DNS Prefetch来优化DNS解析!!
     + 基于HTTP/2.0来代替HTTP/1.1
       + 基于新的二进制格式,让传输的内容更加丰富健壮
       + header压缩,让传输速度更快
       + 服务端推送,可以减少HTTP请求次数
       + 多路复用,让传输速度更快,避免线头阻塞
     + 基于Connection:keep-alive保持TCP通道长链接「HTTP/1.1版本会自动开启」
     + 开启CDN「地域分布式服务器部署」
     + 对于静态资源文件采取“强缓存和协商缓存”,对于不经常更新的数据请求,设置数据缓存!
     + 
第二类:提高页面运行时候的性能
    @1 减少循环嵌套,降低时间复杂度;避免出现死循环/死递归;
    @2 使用事件委托来优化事件绑定,性能可以提高40%+
    @3 减少DOM的回流(重排)和重绘
     + 样式分离读写「渲染队列机制」
     + 批量新增元素「字符串拼接、文档碎片」
     + 基于transform/opacity等方式操作样式
     + 把需要频繁修改样式的元素(例如:JS实现动画)脱离文档流
     + ...
    @4 使用函数的防抖和节流处理高频触发操作
    @5 合理使用闭包,避免内存泄漏,以及手动释放无用的内存!
    @6 减少页面冗余代码,提高代码的重复使用率「也就是尽可能的做封装处理」
    @7 尽可能不要使用for/in循环,因为其消耗性能
    @8 避免使用eval/with等消耗性能的代码
    @9 能用CSS3处理的动画坚决不用JS「JS动画尽可能基于requestAnimationFrame来代替定时器」
    @10 避免使用CSS表达式,因为其非常消耗性能
    @11 CSS选择器的前缀不要过长「因为CSS选择器渲染是从右到左」
    @12 减少table布局「table的渲染是比较消耗性能的」
......
*/

我们平时开发的时候,一般会使用less/sass/stylus等CSS预编译器「需要基于vscode插件把其编译为CSS」;会直接使用ES6的语法「需要基于babel把其转换为ES5,以此兼容IE」;会给CSS3样式加很多的前缀,以此来处理兼容问题……
我们还会基于vscode中的liveserver插件,创建一个本地web服务器,来预览我们的项目…..
我们还要基于各种方案,来解决跨域访问的问题……


以上所有内容,如果都需要开发者手动去处理,那么…..也就没有那么了!!

 

webpack可以帮我们干的事情:

  • 代码转换:TypeScript编译成JavaScript、LESS/SCSS编译成CSS、ES6/7编译为ES5、虚拟DOM编译为真实的DOM等等…

  • 文件优化:压缩JS、CSS、HTML代码,压缩合并图片,图片BASE64等

  • 代码分割:提取多个页面的公共代码、提取首屏不需要执行部分的代码等

  • 模块合并:把模块分类合并成一个文件

  • 自动刷新:创建本地Web服务器,监听本地源代码的变化,自动重新构建,刷新浏览器

  • 代码校验:Eslint代码规范校验和检测、单元测试等

  • 自动发布:自动构建出线上发布代码并传输给发布系统

  • 跨域处理

  • ……

除了webpack外,它们也可以做这些事情:

  • grunt

  • gulp

  • Parcel

  • vite

  • rollup

  • Turbopack

  • ......

1. CommonJS规范 & ES6Module规范

未来的项目一定是 模块化/组件化 开发!!

模块化编程进化历史

  • 单例设计模式

  • AMD「require.js」

  • CommonJS

  • CMD「sea.js」

  • ES6Module

单例设计模式
解决问题:模块间相互冲突的问题「闭包」 ; 模块间相互引用的问题;
不足之处:需要手动分析模块之间的依赖关系,按顺序依次导入相关模块;所有模块都是基于script一个个导入的,这样页面HTTP请求变多!

let xxxModule = (function () {
   let time = new Date();
   const query = function query() {
       // ...
   };
   const handle = function handle() {
       // ...
   };

   // 把供其它板块调用的方法,暴露到全局对象上
   //「局限:暴露的内容比较多,则还会引发全局变量冲突」
   // window.query = query;

   return {
       query,
       handle
   };
})();

 AMD「require.js」 RequireJS
优势:在保证模块之间独立和可以相互访问的基础上,HTML中无需再导入各个模块了「不存在顺序问题」,也不需要自己去分析相互间的依赖关系!
不足:依赖模块的导入是“前置导入”,只有把依赖模块动态导入完毕,才会触发回调函数执行「阻碍代码执行速度」;代码书写的顺序也不是很方便;可能存在重复导入;

//main.js
require.config({
    //全局配置
    baseUrl: 'js/lib',
});

require(['moduleB', 'moduleA'], function (moduleB, moduleA) {
    console.log(moduleB.average(10, 20, 30, 40, 50));
});

//moduleA.js
define(function () {
    return {
        sum(...args) {
            let len = args.length,
                firstItem = args[0];
            if (len === 0) return 0;
            if (len === 1) return firstItem;
            return args.reduce((total, item) => {
                return total + item;
            });
        }
    };
});

//moduleB.js
define(['moduleA'], function (moudleA) {
    return {
        // 求平均数(去掉最大最小值)
        average(...args) {
            let len = args.length,
                firstItem = args[0];
            if (len === 0) return 0;
            if (len === 1) return firstItem;
            args.sort((a, b) => a - b);
            args.pop();
            args.shift();
            return (moudleA.sum(...args) / args.length).toFixed(2);
        }
    };
});

AMD「require.js」 RequireJS
优势:在保证模块之间独立和可以相互访问的基础上,HTML中无需再导入各个模块了「不存在顺序问题」,也不需要自己去分析相互间的依赖关系!
不足:依赖模块的导入是“前置导入”,只有把依赖模块动态导入完毕,才会触发回调函数执行「阻碍代码执行速度」;代码书写的顺序也不是很方便;可能存在重复导入;

//main.js
require.config({
    //全局配置
    baseUrl: 'js/lib',
});

require(['moduleB', 'moduleA'], function (moduleB, moduleA) {
    console.log(moduleB.average(10, 20, 30, 40, 50));
});

//moduleA.js
define(function () {
    return {
        sum(...args) {
            let len = args.length,
                firstItem = args[0];
            if (len === 0) return 0;
            if (len === 1) return firstItem;
            return args.reduce((total, item) => {
                return total + item;
            });
        }
    };
});

//moduleB.js
define(['moduleA'], function (moudleA) {
    return {
        // 求平均数(去掉最大最小值)
        average(...args) {
            let len = args.length,
                firstItem = args[0];
            if (len === 0) return 0;
            if (len === 1) return firstItem;
            args.sort((a, b) => a - b);
            args.pop();
            args.shift();
            return (moudleA.sum(...args) / args.length).toFixed(2);
        }
    };
});

CommonJS模块化规范
唯一的问题:浏览器端不支持CommonJS规范
淘宝“玉伯”仿照CommonJS规范,研发了一款插件 sea.js ,旨在把CommonJS规范搬到浏览器端运行「这种模块思想被称之为CMD」

/* ---A.js--- */
const sum = function sum(...params) {
    return params.reduce((result, item) => {
        return result + item;
    });
};
// 暴露一些API
module.exports = {
    sum
};

/* ---B.js--- */
let name = '哈哈';
let A = require('./A');
const average = function average(...params) {
    return (A.sum(...params) / params.length).toFixed(2);
};
module.exports = {
    average
};

/* ---main.js--- */
let A = require('./A');
console.log(A.sum(10, 20, 30, 40));
let B = require('./B');
console.log(B.average(10, 20, 30, 40));

ES6Module模块规范
ES6 入门教程
可以直接运行在浏览器端;不支持IE;

/* 
模块的导入导出:
  整体来讲,每个模块导出的是一个Module对象,我们可以基于 export/export default 为对象中加入导出的内容
    @1 export default xxx;
      + xxx可以是任意类型值
      + 给Module对象新增一个 “default” 属性,属性值就是导出的值
      + 在一个模块中,export default只允许出现一次
    @2 export 创建值的表达式(声明变量 = 值);
      let num = 10;
      export num; //这样写是错误的
      export let num = 10; //这样写是正确的
      ---
      let num = 10;
      export {
        num, //这样写是可以的
        x:100 //这样写是错误的
      };
      ---
      + export可以出现多次,导出多个内容
      + 声明的变量叫啥名字,最后给Module对象加入的是同名的属性名,属性值就是变量的值

  我们会基于 import 方法导入模块「导入模块导出的内容」
    @1 import N from '模块地址(在浏览器端不能忽略.js后缀名)';
     + N获取的不是整个导出的Module对象,仅仅获取的是Module中default这一项的值
     + 不能在import期间,直接对N解构赋值,因为涉及解构赋值就会变为另外一种情况
    @2 import {x,y} from '模块地址';
     + 直接对导出的Module对象中(排除default属性),导出的内容进行解构赋值
    @3 import * as M from '模块地址';
     + 先导入所有导出的内容,赋值给M变量
*/
2.webpack的基础操作

安装

// 为防止全局安装webpack导致版本冲突,真实项目中以本地安装为主 
$ npm init -y
$ npm install webpack webpack-cli --save-dev
OR
$ yarn add webpack webpack-cli -D

零配置使用

/*
 * 默认会打包SRC目录中的JS文件(入口默认index.js)
 * 打包完成的目录默认是DIST/MAIN.JS
 * webpack默认支持CommonJS和ES6 Module的模块规范,依此进行依赖打包
 */
$ npx webpack

自定义基础配置

webpack.config.js

// Node内置的路径处理模块
const path = require('path');

// 导出自定义配置项
module.exports = {
    // 环境模式「生产环境:production   开发环境:development」
    mode: 'production',
    // 打包入口「相对路径」
    entry: './src/index.js',
    // 打包出口
    output: {
        // 生成的文件名. [hash]创建随机哈希值
        filename: 'bundle.[hash].js',
        // 打包地址「绝对路径」
        path: path.resolve(__dirname, 'dist')
    }
};

 html-webpack-plugin

HtmlWebpackPlugin | webpack 中文文档 | webpack中文文档 | webpack中文网
$ yarn add html-webpack-plugin -D

const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
    ...
    plugins: [
        new HtmlWebpackPlugin({
            // 指定视图模板
            template: './public/index.html',
            // 编译后视图的名字
            filename: 'index.html',
            // 是否压缩
            minify: true
        })
    ]
}

 多入口 & 多出口

const HtmlWebpackPlugin = require('html-webpack-plugin');
// HtmlWebpackPlugin需要配置多套
const htmlPlugins = ['index', 'login'].map(chunk => {
    return new HtmlWebpackPlugin({
        template: `./public/${chunk}.html`,
        filename: `${chunk}.html`,
        // 指定导入的JS
        chunks: [chunk],
        minify: true
    });
});
module.exports = {
    mode: 'production',
    // 配置多入口
    entry: {
        index: "./src/index.js",
        login: "./src/login.js",
    },
    // 出口采用相同的规则
    output: {
        filename: "[name].[hash].js",
        path: path.resolve(__dirname, "dist")
    },
    plugins: [
        ...htmlPlugins
    ]
};

clean-webpack-plugin

$ yarn add clean-webpack-plugin -D

const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
    ...
    plugins: [
        new CleanWebpackPlugin()
    ]
};

 webpack-dev-server

DevServer | webpack
$ yarn add webpack-dev-server -D

/* paclage.json */
"scripts": {
   "start": "webpack server",
   "build": "webpack"
}

/* webpack.config.js */
module.exports = {
    ... 
    devServer: {
        // 域名
        host: '127.0.0.1',
        // 断口号
        port: 3000,
        // GZIP压缩
        compress: true,
        // 自动打开浏览器
        open: true,
        // 热更新
        hot: true,
        // 跨域代理
        proxy: {
            "/jian": {
                target: "https://www.jianshu.com/asimov",
                changeOrigin: true,
                ws: true,
                pathRewrite: { "^/jian": "" }
            },
            "/zhi": {
                target: "https://news-at.zhihu.com/api/4",
                changeOrigin: true,
                ws: true,
                pathRewrite: { "^/zhi": "" }
            }
        }
    }
};
/* 
测试接口 
  简书:
  https://www.jianshu.com/asimov/subscriptions/recommended_collections
  知乎:
  https://news-at.zhihu.com/api/4/news/latest
*/

 处理样式的loader「加载器」

$ yarn add css-loader style-loader less less-loader autoprefixer postcss-loader -D

module.exports = {
    ...
    /* 配置模块加载器LOADER,执行顺序:从右向左、从下向上 */
    module: {
        rules: [{
            test: /\.(css|less)$/, // 基于正则匹配哪些模块需要处理
            use: [
                "style-loader", // 把CSS插入到HEAD中
                "css-loader", // 编译解析@import/URL()这种语法
                "postcss-loader", // 设置前缀
                "less-loader" // 把LESS编译为CSS
            ]
        }]
    }
};

 postcss.config.js

module.exports = {
    plugins: [
        require('autoprefixer')
    ]
};

// 或者在配置项中直接处理
{
    loader: "postcss-loader",
    options: {
        postcssOptions: {
            plugins: [
                require('autoprefixer')
            ]
        }
    }
},

package.json OR .browserslistrc

// https://github.com/browserslist/browserslist
"browserslist": [
  "> 1%",
  "last 2 versions",
  "not dead"
]

mini-css-extract-plugin 抽离CSS样式 /ˈekstrækt/

https://www.npmjs.com/package/mini-css-extract-plugin
$ yarn add mini-css-extract-plugin -D

const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
    ...
    plugins: [
        ...
        new MiniCssExtractPlugin({
            // 设置编译后的文件名字
            filename: 'main.[hash].css'
        })
    ],
    ...
    module: {
        rules: [{
            test: /\.(css|less)$/,
            use: [
                // 使用插件中的LOADER代替STYLE方式
                MiniCssExtractPlugin.loader,
                ...
            ]
        }]
    }
};

 基于babel实现ES6的转换

$ yarn add babel babel-loader @babel/preset-env @babel/core -D

$ yarn add @babel/polyfill

module.exports = {
    ...
    module: {
        rules: [{
            test: /\.js$/,
            use: ['babel-loader'],
            // 设置编译时忽略的文件和指定编译目录
            include: path.resolve(__dirname, 'src'),
            exclude: /node_modules/
        }]
    }
};

 babel.config.js

module.exports = {
    presets: [
        "@babel/preset-env"
    ]
};

index.js

import '@babel/polyfill';

设置优化项:压缩CSS/JS

$ yarn add css-minimizer-webpack-plugin terser-webpack-plugin -D 

const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerWebpackPlugin = require('css-minimizer-webpack-plugin');
module.exports = {
    optimization: {
        // 设置压缩方式
        minimizer: [
            new CssMinimizerWebpackPlugin(),
            new TerserPlugin()
        ]
    }
};

设置解析器:配置别名

module.exports = {
    resolve: {
        alias: {
            '@': path.resolve(__dirname, './src')
        }
    }
};

图片的处理

$ yarn add file-loader url-loader -D

module.exports = {
    ...
    module: {
        rules: [
        ...
        {
            test: /\.(png|jpe?g|gif)$/i,
            type: 'javascript/auto',
            use: [{
                // 把指定大小内的图片BASE64
                loader: 'url-loader',
                options: {
                    limit: 200 * 1024,
                    esModule: false,
                    name: 'images/[name].[hash].[ext]'
                }
            }]
        }]
    },
    // 设置打包的最大资源大小
    performance: {
        maxAssetSize: 100 * 1024 * 1024 * 1024,
        maxEntrypointSize: 100 * 1024 * 1024 * 1024
    }
};

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Brilliant Nemo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值