前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • JavaScript Part 1

    • Ajax总结篇
    • Canvas 绘制八大行星
    • DOM编程之API学习总结篇
    • JavaScript事件机制
    • JavaScript代码片段100个
    • JavaScript作用域分析总结
    • JavaScript原型链回顾
    • JavaScript及jQuery中的各种宽高属性图解
    • JavaScript工程项目的一系列最佳实践
    • JavaScript常用API合集
    • JavaScript数组、字符串、对象常用方法
    • JavaScript数组方法总结篇
    • JavaScript深浅拷贝
    • JavaScript运动框架之速度时间版本
    • JavaScript运行机制Event Loop
    • JavaScript防抖节流原理
    • Javascript中的复制粘贴功能
    • Javascript数组详解
    • OOP之原型与原型链
    • Object.defineProperty详解
    • V8源码浅析JS数组常见方法
    • await 在 forEach 中不生效解决方案
    • iframe+表单跨域提交POST请求
    • javascript笔记总结篇
    • 业务中处理数据结构常用的JS方法
    • 作用域
    • 你真的掌握变量和类型了吗
    • 前后端分离之数据Mock
    • 原型与原型链
    • 原生JS补给(上)
    • 如何写出一个惊艳面试官的深拷贝
    • 带你填一些JS容易出错的坑
    • 彻底弄懂 JavaScript 执行机制
    • 执行上下文 执行栈
    • 正则回顾总结
    • 正则基础知识
    • 正则完整篇
    • 正则表达式
    • 浅析Promise原理
    • 浅谈JavaScript中的异步处理
    • 深拷贝 vs 浅拷贝
    • 聊一聊typeof instanceof 实现原理.
    • 聊一聊闭包
    • 高阶函数map reduce filter
  • JavaScript Part 2

  • CSS

  • HTML

  • Jquery

  • ES6

  • 小程序

  • Vue

  • React

  • 深入React

  • React Native

  • NodeJS

  • Angular

  • TypeScript

  • Webpack

  • 浏览器

  • 移动端

  • 前端工程化

  • Electron

  • HTTP

  • Nginx

  • Linux

  • 数据结构与算法

  • LeetCode算法题

  • 综合

完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

优化篇|博客系列

# 一、删除未引用代码

在实际开发中, 我们无意间可能会产生很多未使用的代码, 但是你又因为业务的原因不想把它删除, 同时又不希望在打包的时候将这些无用的代码包含进去.

# 1.1 Tree shaking

像刚刚我描述的这个移除js上下文中未引用的代码就被称为**tree shaking**, 它依赖于 ES2015 模块系统中的 静态结构特性,例如 import 和 export。

而在webpack4中, 也扩展了这样的能力, 让我们看看它具体是如何使用的.

# 1.2 一个小案例

让我们先来看一个案例, 以确保让你能够完全了解tree shaking.

  • 在src文件夹下新建一个math.js并导出两个方法:
export function square(x) {
    return x * x;
}

export function cube(x) {
    return x * x * x;
}
@前端进阶之旅: 代码已经复制到剪贴板
  • 在src/index.js中使用cube()方法:
import './style.css'
+ import { cube } from './math'

function component() {
    var element = document.createElement('div');

    element.innerHTML = '孔子曰:中午不睡,下午崩溃!孟子曰:孔子说的对!';
    element.classList.add('color_red')

+   console.log(cube(3)) // 使用了cube
    return element;
}

document.body.appendChild(component());
@前端进阶之旅: 代码已经复制到剪贴板
  • 修改webpack.config.js中的mode:
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
+   mode: 'development',
    entry: [
        './src/index.js'
    ],
    output: {
        filename: '[name].bundle.js',
        path: path.resolve(__dirname, 'dist')
    },
    plugins: [
    	new HtmlWebpackPlugin({
    	 	title: 'html-webpack',
    	})
    ],
    module: {
        rules: [{
                test: /\.css$/,
                use: [
                    "style-loader",
                    "css-loader"
                ]
            }
        ]
    }
}
@前端进阶之旅: 代码已经复制到剪贴板

完成上述的三个步骤之后, 执行npm run build指令, 来进行打包.

可以看到生成的dist文件夹目录长这样:

/dist
	|- index.html
	|- main.bundle.js
@前端进阶之旅: 代码已经复制到剪贴板

让我们打开main.bundle.js看看里面的内容, 使用Ctrl + F查找square, 发现它竟然还是能够搜索的到.

这就是我想要表达的:

明明square方法没有在代码使用, 但是它还是会被包含在构建完之后的bundle中.

# 1.3 找出未引用代码

通过上面👆这个案例, 我想你大概已经明白未引用代码的意思了吧.

现在让我们来看看如何在打包的时候找出这些未引用的代码.

说是找出这些未引用的代码, 但其实这种说法是不太全面的.

而是要找出并删除这些无副作用的代码.

「副作用」的定义是,在导入时会执行特殊行为的代码,而不是仅仅暴露一个 export 或多个 export。

有些代码是有副作用的, 但是你也不能把它删除.

比如polyfill, 它会影响全局作用域, 但是它通常不会提供export.

而将文件标记为无副作用(也就是纯粹部分), 是通过在package.json设置sideEffects属性来实现的:

{
  "name": "your-project",
  "sideEffects": false
}
@前端进阶之旅: 代码已经复制到剪贴板

若是将sideEffects设置成了false则表示所有文件都是无副作用的, 来告知webpack你可以大胆放心的删除未用到的export导出.

如果你的代码中确实又一些副作用, 那么可以改为提供一个数组:

{
  "name": "your-project",
  "sideEffects": [
    "./src/some-side-effectful-file.js",
    "*.css"
  ]
}
@前端进阶之旅: 代码已经复制到剪贴板

# 1.4 压缩输出

我们已经明白了什么是未使用的代码, 也知道了利用sideEffects找到这些未使用的代码.

其实如果你没有在package.json中设置sideEffects也没事, 它默认会认为所有的文件都是无副作用可供webpack删除的.

所以接下来就是要在构建的时候将它们从bundle中删除.

像这种删除未使用代码并进行代码压缩就被称为压缩输出, 非常好记.

而实现压缩输出的方式, 是要启用webpack内部的uglifyjs插件.

它主要有以下几种方式启用:

  • webpack4直接通过mode配置成production就可以了
  • webpack4如果没有配置mode的话它默认也会启用
  • 通过在命令行中添加--optimize-minimize, 比如"build": "webpack --optimize-minimize"

通过压缩输出, 我们此时再看看npm run build之后的效果:

  • main.bundle.js里的代码被压缩成了我们看不懂的代码…

  • 同时也查找不到square函数了.

# 1.5 总结

其实说了这么多, 一是为了介绍什么是tree shaking, 二就是为了说明压缩输出的实现.

这项优化功能在我们实际开发中有很大的作用.

# 二、代码分离

代码分离的特性主要是: 能够把代码分离到不同的bundle中, 然后按需加载或并行加载这些模块.

常用的代码分离方法:

  • 多个入口起点, 通过配置entry
  • 防止重复, 使用webpack.optimize.CommonsChunkPlugin插件去重和分离chunk (但是在webpack4中已废弃)
  • 动态导入: 通过模块的内联函数调用来分离代码。

# 2.1 多个入口起点

通过在webpack.config.js设置entry就可以了:

const path = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: {
    index: './src/index.js',
    another: './src/another-module.js'
  },
  plugins: [
    new HTMLWebpackPlugin({
      title: 'Webpack Code Splitting'
    })
  ],
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
};
@前端进阶之旅: 代码已经复制到剪贴板

# 2.2 防止重复(CommonsChunkPlugin废弃)

在学习官网给出的文档 《 webpack文档-代码分离-防止重复》这一章节的时候, 发现使用webpack.optimize.CommonsChunkPlugin之后, 在构建的过程中会报错.

后来了解到这个插件在webpack4中已经被废弃了, webpack4中已经默认做了这方面的优化了.

这里我就简单介绍一下CommonsChunkPlugin这个功能就是了, 如果你对此不感兴趣可以跳过这块内容阅读下一节.

案例🌰:

此案例基于webpack3, GitHub案例地址: LinDaiDai/webpack3-demo

假设我们有这样一个场景:

lodash这个依赖在不同的js文件中被使用, 例如在index.js和another-module.js中都被使用了:

(lodash是一个工具库里面提供了很多实用的方法,可以使用npm i lodash安装)

// src/index.js
import _ from 'lodash'
@前端进阶之旅: 代码已经复制到剪贴板
// src/another-module.js
import _ from 'lodash'
@前端进阶之旅: 代码已经复制到剪贴板

然后在webpack.config.js中配置两个入口:

const path = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: {
    index: './src/index.js',
    another: './src/another-module.js'
  },
  plugins: [
    new HTMLWebpackPlugin({
      title: 'Webpack Code Splitting'
    })
  ],
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
};
@前端进阶之旅: 代码已经复制到剪贴板

此时如果你执行npm run build进行打包, 会看到:

lindaidai@LinDaiDaideMacBook-Pro webpack3-demo % npm run build

> webpack3-demo@1.0.0 build /Users/lindaidai/codes/webpack/webpack3-demo
> webpack

Hash: 273be9a48e0799049788
Version: webpack 3.12.0
Time: 422ms
            Asset       Size  Chunks                    Chunk Names
+ index.bundle.js     546 kB    0, 1  [emitted]  [big]  index
+ another.bundle.js     545 kB       1  [emitted]  [big]  another
       index.html  249 bytes          [emitted]         
   [1] (webpack)/buildin/global.js 509 bytes {0} {1} [built]
   [2] (webpack)/buildin/module.js 517 bytes {0} {1} [built]
   [3] ./src/another-module.js 122 bytes {0} {1} [built]
   [4] ./src/index.js 274 bytes {0} [built]
    + 1 hidden module
Child html-webpack-plugin for "index.html":
     1 asset
       [2] (webpack)/buildin/global.js 509 bytes {0} [built]
       [3] (webpack)/buildin/module.js 517 bytes {0} [built]
        + 2 hidden modules
@前端进阶之旅: 代码已经复制到剪贴板

生成的index.bundle.js和another.bundle.js中都有lodash依赖, 导致依赖重复, 生成的bundle很大.

dist文件目录为:

← 三种hash的区别基于gulp的工作流配置 →

fe
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • JavaScript Part 1

    • Ajax总结篇
    • Canvas 绘制八大行星
    • DOM编程之API学习总结篇
    • JavaScript事件机制
    • JavaScript代码片段100个
    • JavaScript作用域分析总结
    • JavaScript原型链回顾
    • JavaScript及jQuery中的各种宽高属性图解
    • JavaScript工程项目的一系列最佳实践
    • JavaScript常用API合集
    • JavaScript数组、字符串、对象常用方法
    • JavaScript数组方法总结篇
    • JavaScript深浅拷贝
    • JavaScript运动框架之速度时间版本
    • JavaScript运行机制Event Loop
    • JavaScript防抖节流原理
    • Javascript中的复制粘贴功能
    • Javascript数组详解
    • OOP之原型与原型链
    • Object.defineProperty详解
    • V8源码浅析JS数组常见方法
    • await 在 forEach 中不生效解决方案
    • iframe+表单跨域提交POST请求
    • javascript笔记总结篇
    • 业务中处理数据结构常用的JS方法
    • 作用域
    • 你真的掌握变量和类型了吗
    • 前后端分离之数据Mock
    • 原型与原型链
    • 原生JS补给(上)
    • 如何写出一个惊艳面试官的深拷贝
    • 带你填一些JS容易出错的坑
    • 彻底弄懂 JavaScript 执行机制
    • 执行上下文 执行栈
    • 正则回顾总结
    • 正则基础知识
    • 正则完整篇
    • 正则表达式
    • 浅析Promise原理
    • 浅谈JavaScript中的异步处理
    • 深拷贝 vs 浅拷贝
    • 聊一聊typeof instanceof 实现原理.
    • 聊一聊闭包
    • 高阶函数map reduce filter
  • JavaScript Part 2

  • CSS

  • HTML

  • Jquery

  • ES6

  • 小程序

  • Vue

  • React

  • 深入React

  • React Native

  • NodeJS

  • Angular

  • TypeScript

  • Webpack

  • 浏览器

  • 移动端

  • 前端工程化

  • Electron

  • HTTP

  • Nginx

  • Linux

  • 数据结构与算法

  • LeetCode算法题

  • 综合