面试官最喜欢问☞工程化专题

3,106 阅读15分钟

这是我参与8月更文挑战的第12天,活动详情查看:8月更文挑战

git知道嘛?用过吗,说了下项目中使用的基本命令,commit status checkout log pull push branch

如何不影响远程代码,将本地代码回顾到之前的一个状态? git checkout 某个hash值

git stash 暂存区的概念

项目

聊聊你的项目

项目使用了那些技术、为啥使用这些技术

是用的vue-cli?vue的版本是?脚手架版本?

聊下脚手架创建的项目的结构 ,为啥是这个结构 =》很多组件,你怎么去管理,全部一个文件夹呢? =》你说分目录,大概的依据是啥

项目是spa嘛?多少路由?=》父子路由 =》异步组件加载

了解过优化嘛?

讲了很多,分渲染优化和打包优化说的(js,style放置的位置,使用缓存、CDN、webpack打包时候的tree-shaking),面试官说还可以,优化分类可以从线下、线上、...很多方面去讲,除了我讲的,还有网络优化和其他啥的

webpack

  1. 项目工程的理解,比如vue-cli脚手架创建的各个文件模块的作用,存在的必要性的时候,当时答的比较乱,也不够系统 你学的比较深入的点有?我说最近再看webpack就开始webpack了

webpack的基本概念?作用是啥?

聊了一些module和里面的一些常见loader(url,file,css,style) ,

插件知道嘛?简单说了一下, htmlwebpackplugin

分割?多入口 =》merge,

抽离CSS、=》MiniCssExtractPlugin.loader,

抽离公共代码=》 为啥抽离,第三方和公共模块 =》 splitChunks

WDS webpackDevServer 聊了一下, 热更新? 配置 hot = true

聊了下 babel,babel-ployfill,babel-runtime (为啥需要babel,babel-ployfill又干了啥?,babel-runtime知道嘛?)

  1. 算法题leetcode 112路径总和

目录

  • 前言
  • webpack
  • babel
  • Gulp
  • Git

前言

本专题按照以下几个方便进行整理:

  • webpack
  • Gulp

本专题将持续更新,集前端面试常见工程化高频知识点于一篇,各位同学可根据自己技术栈,进行选取学习。适合初次全面复习的同学,查缺补漏,知识面比较全,复习完成后,再按照本人整理的面试高频题配合复习,使得找工作事半功倍,一定要理解,不要死记硬背,对于一些概念性的和原理的内容要深入理解。

“你从头读,尽量往下读,直到你一窍不通时,再从头开始,这样坚持往下读,直到你完全读懂为止。”

webpack

webpack 做过哪些优化,开发效率方面、打包策略方面等等

1)优化 Webpack 的构建速度

  • 使用高版本的 Webpack (使用webpack4)

  • 多线程/多实例构建:HappyPack(不维护了)、thread-loader

  • 缩小打包作用域:

    • exclude/include (确定 loader 规则范围)
    • resolve.modules 指明第三方模块的绝对路径 (减少不必要的查找)
    • resolve.extensions 尽可能减少后缀尝试的可能性
    • noParse 对完全不需要解析的库进行忽略 (不去解析但仍会打包到 bundle 中,注意被忽略掉的文件里不应该包含 import、require、define 等模块化语句)
    • IgnorePlugin (完全排除模块)
    • 合理使用alias
  • 充分利用缓存提升二次构建速度:

    • babel-loader 开启缓存
    • terser-webpack-plugin 开启缓存
    • 使用 cache-loader 或者 hard-source-webpack-plugin
      注意:thread-loader 和 cache-loader 兩個要一起使用的話,請先放 cache-loader 接著是 thread-loader 最後才是 heavy-loader
  • DLL:

    • 使用 DllPlugin 进行分包,使用 DllReferencePlugin(索引链接) 对 manifest.json 引用,让一些基本不会改动的代码先打包成静态资源,避免反复编译浪费时间。

2)使用webpack4-优化原因

  • (a)V8带来的优化(for of替代forEach、Map和Set替代Object、includes替代indexOf)
  • (b)默认使用更快的md4 hash算法
  • (c)webpacks AST可以直接从loader传递给AST,减少解析时间
  • (d)使用字符串方法替代正则表达式

①noParse

  • 不去解析某个库内部的依赖关系
  • 比如jquery 这个库是独立的, 则不去解析这个库内部依赖的其他的东西
  • 在独立库的时候可以使用
module.exports = {
  module: {
    noParse: /jquery/,
    rules:[]
  }
}

②IgnorePlugin

  • 忽略掉某些内容 不去解析依赖库内部引用的某些内容
  • 从moment中引用 ./locol 则忽略掉
  • 如果要用local的话 则必须在项目中必须手动引入
import 'moment/locale/zh-cn'
module.exports = {
    plugins: [
        new Webpack.IgnorePlugin(/./local/, /moment/),
    ]
}

③dillPlugin

  • 不会多次打包, 优化打包时间
  • 先把依赖的不变的库打包
  • 生成 manifest.json文件
  • 然后在webpack.config中引入
  • webpack.DllPlugin Webpack.DllReferencePlugin

④happypack -> thread-loader

  • 大项目的时候开启多线程打包
  • 影响前端发布速度的有两个方面,一个是构建,一个就是压缩,把这两个东西优化起来,可以减少很多发布的时间。

⑤thread-loader
thread-loader 会将您的 loader 放置在一个 worker 池里面运行,以达到多线程构建。
把这个 loader 放置在其他 loader 之前(如下图 example 的位置), 放置在这个 loader 之后的 loader 就会在一个单独的 worker 池(worker pool)中运行。

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /.js$/,
        include: path.resolve("src"),
        use: [
          "thread-loader",
          // 你的高开销的loader放置在此 (e.g babel-loader)
        ]
      }
    ]
  }
}

每个 worker 都是一个单独的有 600ms 限制的 node.js 进程。同时跨进程的数据交换也会被限制。请在高开销的loader中使用,否则效果不佳

⑥压缩加速——开启多线程压缩

  • 不推荐使用 webpack-paralle-uglify-plugin,项目基本处于没人维护的阶段,issue 没人处理,pr没人合并。
    Webpack 4.0以前:uglifyjs-webpack-plugin,parallel参数
module.exports = {
  optimization: {
    minimizer: [
      new UglifyJsPlugin({
        parallel: true,
      }),
    ],
  },};
  • 推荐使用 terser-webpack-plugin
module.exports = {
  optimization: {
    minimizer: [new TerserPlugin(
      parallel: true   // 多线程
    )],
  },
};

2)优化 Webpack 的打包体积

  • 压缩代码

    • webpack-paralle-uglify-plugin
    • uglifyjs-webpack-plugin 开启 parallel 参数 (不支持ES6)
    • terser-webpack-plugin 开启 parallel 参数
    • 多进程并行压缩
    • 通过 mini-css-extract-plugin 提取 Chunk 中的 CSS 代码到单独文件,通过optimize-css-assets-webpack-plugin插件 开启 cssnano 压缩 CSS。
  • 提取页面公共资源:

    • 使用 html-webpack-externals-plugin,将基础包通过 CDN 引入,不打入 bundle 中
    • 使用 SplitChunksPlugin 进行(公共脚本、基础包、页面公共文件)分离(Webpack4内置) ,替代了 CommonsChunkPlugin 插件
    • 基础包分离:将一些基础库放到cdn,比如vue,webpack 配置 external是的vue不打入bundle
  • Tree shaking

    • purgecss-webpack-plugin 和 mini-css-extract-plugin配合使用(建议)
    • 打包过程中检测工程中没有引用过的模块并进行标记,在资源压缩时将它们从最终的bundle中去掉(只能对ES6 Modlue生效) 开发中尽可能使用ES6 Module的模块,提高tree shaking效率
    • 禁用 babel-loader 的模块依赖解析,否则 Webpack 接收到的就都是转换过的 CommonJS 形式的模块,无法进行 tree-shaking
    • 使用 PurifyCSS(不在维护) 或者 uncss 去除无用 CSS 代码
  • Scope hoisting

    • 构建后的代码会存在大量闭包,造成体积增大,运行代码时创建的函数作用域变多,内存开销变大。Scope hoisting 将所有模块的代码按照引用顺序放在一个函数作用域里,然后适当的重命名一些变量以防止变量名冲突
    • 必须是ES6的语法,因为有很多第三方库仍采用 CommonJS 语法,为了充分发挥 Scope hoisting 的作用,需要配置 mainFields 对第三方模块优先采用 jsnext:main 中指向的ES6模块化语法
  • 图片压缩

    • 使用基于 Node 库的 imagemin (很多定制选项、可以处理多种图片格式)
    • 配置 image-webpack-loader
  • 动态Polyfill

    • 建议采用 polyfill-service 只给用户返回需要的polyfill,社区维护。(部分国内奇葩浏览器UA可能无法识别,但可以降级返回所需全部polyfill)
    • @babel-preset-env 中通过useBuiltIns: 'usage参数来动态加载polyfill。

3)speed-measure-webpack-plugin
简称 SMP,分析出 Webpack 打包过程中 Loader 和 Plugin 的耗时,有助于找到构建过程中的性能瓶颈。

开发阶段

1)开启多核压缩

插件:** terser-webpack-plugin **

const TerserPlugin = require('terser-webpack-plugin')
module.exports = {
    optimization: {
        minimizer: [
            new TerserPlugin({
                parallel: true,
                terserOptions: {
                    ecma: 6,
                },
            }),
        ]
    }
}

2)监控面板

插件:speed-measure-webpack-plugin
在打包的时候显示出每一个loader,plugin所用的时间,来精准优化

// webpack.config.js文件
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
const smp = new SpeedMeasurePlugin();
//............
// 用smp.warp()包裹一下合并的config
module.exports = smp.wrap(merge(_mergeConfig, webpackConfig));

3)开启一个通知面板

插件:webpack-build-notifier

// webpack.config.js文件
const WebpackBuildNotifierPlugin = require('webpack-build-notifier');
const webpackConfig= {
    plugins: [
        new WebpackBuildNotifierPlugin({
            title: '我的webpack',
            // logo: path.resolve('./img/favicon.png'),
            suppressSuccess: true
        })
    ]
}

4)开启打包进度

插件:progress-bar-webpack-plugin

// webpack.config.js文件
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
const webpackConfig= {
    plugins: [
        new ProgressBarPlugin(),
    ]
}

5)开发面板更清晰

插件:webpack-dashboard

// webpack.config.js文件
const DashboardPlugin = require('webpack-dashboard/plugin');
const webpackConfig= {
    plugins: [
        new DashboardPlugin()
        ]
}
// package.json文件
{
  "scripts": {
    "dev": "webpack-dashboard webpack --mode development",
  },
}

6)开启窗口的标题

node-bash-title
这个包mac的item用有效果,windows暂时没看到效果

// webpack.config.js文件
const setTitle = require('node-bash-title');
setTitle('server');

7)friendly-errors-webpack-plugin

插件:friendly-errors-webpack-plugin

new FriendlyErrorsWebpackPlugin({
    compilationSuccessInfo: {
        messages: ['You application is running here http://localhost:3000'],
        notes: ['Some additionnal notes to be displayed unpon successful compilation']
    },
    onErrors: function (severity, errors) {
        // You can listen to errors transformed and prioritized by the plugin
        // severity can be 'error' or 'warning'
    },
    // should the console be cleared between each compilation?
    // default is true
    clearConsole: true,
    
    // add formatters and transformers (see below)
    additionalFormatters: [],
    additionalTransformers: []
}),

Babel

简单描述一下 Babel 的编译过程

Babel 是一个 JavaScript 编译器,是一个工具链,主要用于将采用 ECMAScript 2015+ 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。

Babel 本质上就是在操作 AST 来完成代码的转译。AST是抽象语法树(Abstract Syntax Tree, AST)

如果想要了解更多,可以阅读和尝试:

Babel 的功能很纯粹,它只是一个编译器。大多数编译器的工作过程可以分为三部分:

  1. 解析(Parse):将源代码转换成更加抽象的表示方法(例如抽象语法树)。包括词法分析和语法分析。词法分析主要把字符流源代码(Char Stream)转换成令牌流( Token Stream),语法分析主要是将令牌流转换成抽象语法树(Abstract Syntax Tree,AST)。

  2. 转换(Transform):通过 Babel 的插件能力,对(抽象语法树)做一些特殊处理,将高版本语法的 AST 转换成支持低版本语法的 AST。让它符合编译器的期望,当然在此过程中也可以对 AST 的 Node 节点进行优化操作,比如添加、更新以及移除节点等。

  3. 生成(Generate):将 AST 转换成字符串形式的低版本代码,同时也能创建 Source Map 映射。

Parse(解析)

一般来说,Parse 阶段可以细分为两个阶段:词法分析(Lexical Analysis, LA)和语法分析(Syntactic Analysis, SA)。

词法分析

词法分析阶段可以看成是对代码进行“分词”,它接收一段源代码,然后执行一段 tokenize 函数,把代码分割成被称为 Tokens 的东西Tokens 是一个数组,由一些代码的碎片组成,比如数字、标点符号、运算符号等等等等,

语法分析

词法分析之后,代码就已经变成了一个 Tokens 数组了,现在需要通过语法分析把 Tokens 转化为上面提到过的 AST

Transform(转换)

这一步也就是操作 AST,操作 AST 也就是操作其中的节点,可以增删改这些节点,从而转换成实际需要的 AST

Babel 对于 AST 的遍历是深度优先遍历,对于 AST 上的每一个分支 Babel 都会先向下遍历走到尽头,然后再向上遍历退出刚遍历过的节点,然后寻找下一个分支。

Generate(代码生成)

经过上面两个阶段,需要转译的代码已经经过转换,生成新的 AST 了,最后一个阶段理所应当就是根据这个 AST 来输出代码。

Babel 是通过 @babel-generator 来完成的。当然,也是深度优先遍历。

经过这三个阶段,代码就被 Babel 转译成功了。

Git

Git 常用命令

查看分支:git branch

创建分支:git branch

切换分支:git checkout

创建+切换分支:git checkout -b

合并某分支到当前分支:git merge

删除分支:git branch -d

如何使用Git管理项目

image.png

实际开发中,一个仓库(一般只放一个项目)主要存在两条主分支:master与develop分支。这个两个分支的生命周期是整个项目周期。

我们可能使用的不同类型的分支对项目进行管理是:

  • 功能分支

    功能分支(或有时称为主题分支)用于为即将发布或遥远的未来版本开发新功能。在开始开发某个功能时,将包含该功能的目标版本在那时很可能是未知的。功能分支的本质在于,只要该功能处于开发阶段,它就存在,但最终会被合并回develop(明确将新功能添加到即将发布的版本中)或丢弃。功能分支通常只存在于开发者仓库中,而不存在于origin

    • develop分支拉取,且必须合并回 develop
    • 分支命名约定:除了 masterdeveloprelease-*, 或hotfix-*
  • 发布分支

    发布分支支持准备新的生产版本。它们允许在最后一刻打点 i 和交叉 t。此外,它们允许修复小错误并为发布准备元数据(版本号、构建日期等)。通过在发布分支上完成所有这些工作,该develop 分支被清除以接收下一个大版本的功能。

    • develop分支拉取,且必须合并回 develop 和 master
    • 分支命名约定:release-*
  • 修补程序分支

    Hotfix 分支与发布分支非常相似,因为它们也旨在为新的生产版本做准备,尽管是计划外的。它们产生于需要立即对现场制作版本的不良状态采取行动。当必须立即解决生产版本中的关键错误时,可以从标记生产版本的主分支上的相应标记中分支出一个修补程序分支。

    本质是团队成员(在develop分支上)的工作可以继续,而另一个人正在准备快速生产修复。

    • master分支拉取,且必须合并回 develop 和 master
    • 分支命名约定:hotfix-*

master:这个分支最为稳定,这个分支表明项目处于可发布的状态。

develop:做为开发的分支,平行于master分支。

Feature branches:这种分支和咱们程序员平常开发最为密切,称做功能分支。必须从develop分支建立,完成后合并回develop分支。

Release branches:这个分支用来分布新版本。从develop分支建立,完成后合并回develop与master分支。这个分支上能够作一些很是小的bug修复,固然,你也能够禁止在这个分支作任何bug的修复工做,而只作版本发布的相关操做,例如设置版本号等操做,那样的话那些发现的小bug就必须放到下一个版本修复了。若是在这个分支上发现了大bug,那么也绝对不能在这个分支上改,须要Featrue分支上改,走正常的流程。

Hotfix branches:这个分支主要为修复线上特别紧急的bug准备的。必须从master分支建立,完成后合并回develop与master分支。这个分支主要是解决线上版本的紧急bug修复的,例如忽然版本V0.1上有一个致命bug,必须修复。那么咱们就能够从master 分支上发布这个版本那个时间点 例如 tag v0.1(通常代码发布后会及时在master上打tag),来建立一个 hotfix-v0.1.1的分支,而后在这个分支上改bug,而后发布新的版本。最后将代码合并回develop与master分支。

更多请参考

前端性能优化

  • 减少回流重绘

  • 缩小代码体积,例如:Tree-shaking、代码压缩、代码分割、Scope-hoisting等

  • 减少请求数,例如:雪碧图、基础库打成一个包

  • 并发请求,使用cdn,突破浏览器对同一域名的TCP连接数限制,或者使用http2

  • 运行时加载,例如:图片懒加载,组件动态import

  • 缓存,例如:http缓存,dll等


如果这篇文章帮到了你,记得点赞👍收藏加关注哦😊,希望点赞多多多多...

文中如有错误,欢迎在评论区指正


往期文章