mirror of
https://github.com/zhwei820/learn.lianglianglee.com.git
synced 2025-09-17 08:46:40 +08:00
573 lines
34 KiB
HTML
573 lines
34 KiB
HTML
<!DOCTYPE html>
|
||
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
|
||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||
<head>
|
||
<head>
|
||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
|
||
<link rel="icon" href="/static/favicon.png">
|
||
<title>01 项目基石:前端脚手架工具探秘.md.html</title>
|
||
<!-- Spectre.css framework -->
|
||
<link rel="stylesheet" href="/static/index.css">
|
||
<!-- theme css & js -->
|
||
<meta name="generator" content="Hexo 4.2.0">
|
||
</head>
|
||
|
||
<body>
|
||
|
||
<div class="book-container">
|
||
<div class="book-sidebar">
|
||
<div class="book-brand">
|
||
<a href="/">
|
||
<img src="/static/favicon.png">
|
||
<span>技术文章摘抄</span>
|
||
</a>
|
||
</div>
|
||
<div class="book-menu uncollapsible">
|
||
<ul class="uncollapsible">
|
||
<li><a href="/" class="current-tab">首页</a></li>
|
||
</ul>
|
||
|
||
<ul class="uncollapsible">
|
||
<li><a href="../">上一级</a></li>
|
||
</ul>
|
||
|
||
<ul class="uncollapsible">
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/前端工程化精讲-完/00 开篇词 建立上帝视角,全面系统掌握前端效率工程化.md.html">00 开篇词 建立上帝视角,全面系统掌握前端效率工程化.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
<a class="current-tab" href="/专栏/前端工程化精讲-完/01 项目基石:前端脚手架工具探秘.md.html">01 项目基石:前端脚手架工具探秘.md.html</a>
|
||
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/前端工程化精讲-完/02 界面调试:热更新技术如何开着飞机修引擎?.md.html">02 界面调试:热更新技术如何开着飞机修引擎?.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/前端工程化精讲-完/03 构建提速:如何正确使用 SourceMap?.md.html">03 构建提速:如何正确使用 SourceMap?.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/前端工程化精讲-完/04 接口调试:Mock 工具如何快速进行接口调试?.md.html">04 接口调试:Mock 工具如何快速进行接口调试?.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/前端工程化精讲-完/05 编码效率:如何提高编写代码的效率?.md.html">05 编码效率:如何提高编写代码的效率?.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/前端工程化精讲-完/06 团队工具:如何利用云开发提升团队开发效率?.md.html">06 团队工具:如何利用云开发提升团队开发效率?.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/前端工程化精讲-完/07 低代码工具:如何用更少的代码实现更灵活的需求.md.html">07 低代码工具:如何用更少的代码实现更灵活的需求.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/前端工程化精讲-完/08 无代码工具:如何做到不写代码就能高效交付?.md.html">08 无代码工具:如何做到不写代码就能高效交付?.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/前端工程化精讲-完/09 构建总览:前端构建工具的演进.md.html">09 构建总览:前端构建工具的演进.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/前端工程化精讲-完/10 流程分解:Webpack 的完整构建流程.md.html">10 流程分解:Webpack 的完整构建流程.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/前端工程化精讲-完/11 编译提效:如何为 Webpack 编译阶段提速?.md.html">11 编译提效:如何为 Webpack 编译阶段提速?.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/前端工程化精讲-完/12 打包提效:如何为 Webpack 打包阶段提速?.md.html">12 打包提效:如何为 Webpack 打包阶段提速?.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/前端工程化精讲-完/13 缓存优化:那些基于缓存的优化方案.md.html">13 缓存优化:那些基于缓存的优化方案.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/前端工程化精讲-完/14 增量构建:Webpack 中的增量构建.md.html">14 增量构建:Webpack 中的增量构建.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/前端工程化精讲-完/15 版本特性:Webpack 5 中的优化细节.md.html">15 版本特性:Webpack 5 中的优化细节.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/前端工程化精讲-完/16 无包构建:盘点那些 No-bundle 的构建方案.md.html">16 无包构建:盘点那些 No-bundle 的构建方案.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/前端工程化精讲-完/17 部署初探:为什么一般不在开发环境下部署代码?.md.html">17 部署初探:为什么一般不在开发环境下部署代码?.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/前端工程化精讲-完/18 工具盘点:掌握那些流行的代码部署工具.md.html">18 工具盘点:掌握那些流行的代码部署工具.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/前端工程化精讲-完/19 安装提效:部署流程中的依赖安装效率优化.md.html">19 安装提效:部署流程中的依赖安装效率优化.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/前端工程化精讲-完/20 流程优化:部署流程中的构建流程策略优化.md.html">20 流程优化:部署流程中的构建流程策略优化.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/前端工程化精讲-完/21 容器方案:从构建到部署,容器化方案的优势有哪些?.md.html">21 容器方案:从构建到部署,容器化方案的优势有哪些?.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/前端工程化精讲-完/22 案例分析:搭建基本的前端高效部署系统.md.html">22 案例分析:搭建基本的前端高效部署系统.md.html</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/前端工程化精讲-完/23 结束语 前端效率工程化的未来展望.md.html">23 结束语 前端效率工程化的未来展望.md.html</a>
|
||
|
||
</li>
|
||
</ul>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
|
||
<div class="sidebar-toggle-inner"></div>
|
||
</div>
|
||
|
||
<script>
|
||
function add_inner() {
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
inner.classList.add('show')
|
||
}
|
||
|
||
function remove_inner() {
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
inner.classList.remove('show')
|
||
}
|
||
|
||
function sidebar_toggle() {
|
||
let sidebar_toggle = document.querySelector('.sidebar-toggle')
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
let content = document.querySelector('.off-canvas-content')
|
||
if (sidebar_toggle.classList.contains('extend')) { // show
|
||
sidebar_toggle.classList.remove('extend')
|
||
sidebar.classList.remove('hide')
|
||
content.classList.remove('extend')
|
||
} else { // hide
|
||
sidebar_toggle.classList.add('extend')
|
||
sidebar.classList.add('hide')
|
||
content.classList.add('extend')
|
||
}
|
||
}
|
||
|
||
|
||
function open_sidebar() {
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
sidebar.classList.add('show')
|
||
overlay.classList.add('show')
|
||
}
|
||
function hide_canvas() {
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
sidebar.classList.remove('show')
|
||
overlay.classList.remove('show')
|
||
}
|
||
|
||
</script>
|
||
|
||
<div class="off-canvas-content">
|
||
<div class="columns">
|
||
<div class="column col-12 col-lg-12">
|
||
<div class="book-navbar">
|
||
<!-- For Responsive Layout -->
|
||
<header class="navbar">
|
||
<section class="navbar-section">
|
||
<a onclick="open_sidebar()">
|
||
<i class="icon icon-menu"></i>
|
||
</a>
|
||
</section>
|
||
</header>
|
||
</div>
|
||
<div class="book-content" style="max-width: 960px; margin: 0 auto;
|
||
overflow-x: auto;
|
||
overflow-y: hidden;">
|
||
<div class="book-post">
|
||
<p id="tip" align="center"></p>
|
||
<div><h1>01 项目基石:前端脚手架工具探秘</h1>
|
||
<p>你好,我是李思嘉,前端效率工程化这门课我们会讨论一个前端项目从开发到构建和部署这一系列工作流程的效率问题。在开发效率篇里,我们会讨论一系列影响开发效率的流程和工具。工欲善其事必先利其器,第一课时,我们首先从开发一个新项目时最基础的准备工作讲起。</p>
|
||
<p>当你准备开发一个新项目时,在进入到实际业务编码前,通常需要做很多的基础准备工作,这里会遇到的问题有:</p>
|
||
<ol>
|
||
<li>要准备好一个项目的基础开发设施,需要投入大量时间和精力,这部分的工作计量是以天为单位的。</li>
|
||
<li>一个完备的项目基础环境就像一个精密的仪器,只有各部分都充分协调后才能运转正常。要在较短时间内配置一个技术栈完整、辅助功能丰富、兼顾不同环境下构建优化目标的项目基础代码,通常需要开发人员在工程领域长久的知识储备与实践总结,而这对于经验相对较少的开发人员而言是一个不小的挑战。</li>
|
||
<li>不同的项目需求和团队情况,对应我们在使用基础设施时的选择可能也各不相同,因此我们并不能依靠一套固定不变的模板,而是需要根据不同的现状来使用不同的基础设施。这又增加了整体时间成本。</li>
|
||
</ol>
|
||
<p>而<strong>脚手架工具</strong>,正是为了解决这些问题而诞生的。</p>
|
||
<ul>
|
||
<li>利用脚手架工具,我们可以经过几个简单的选项<strong>快速生成</strong>项目的基础代码。</li>
|
||
<li>使用脚手架工具生成的项目模板通常是经过经验丰富的开发者提炼和检验的,很大程度上代表某一类项目开发的<strong>最佳实践</strong>,相较于让开发者自行配置提供了更优选择。</li>
|
||
<li>同时,脚手架工具也支持使用<strong>自定义模板</strong>,我们也可以根据项目中的实际经验总结、定制一个脚手架模板。</li>
|
||
</ul>
|
||
<p>因此,对于一个熟练的前端工程师来说,要掌握的基本能力之一就是通过技术选型来确定所需要使用的<strong>技术栈</strong>,然后根据技术栈选择合适的<strong>脚手架工具</strong>,来做项目代码的初始化。一个合适的脚手架,可以为开发人员提供反复优化后的开发流程配置,高效地解决开发中涉及的流程问题,使得工程师能够快速上手,并提升整个开发流程的效率和体验。当然,前提是建立在选择对了脚手架工具并深入掌握其工作细节的基础上。</p>
|
||
<p>那么下面我们先来谈谈脚手架工具究竟是什么。</p>
|
||
<h3>什么是脚手架</h3>
|
||
<p>说到<strong>脚手架(Scaffold)</strong> 这个词,相信你并不陌生,它原本是建筑工程术语,指为了保证施工过程顺利而搭建的工作平台,它为工人们在各层施工提供了基础的功能保障。</p>
|
||
<p><img src="assets/Ciqc1F8w7KGAc5KTAFjMHp-GUzQ575.png" alt="Drawing 0.png" /></p>
|
||
<p>而在<strong>软件开发领域</strong>,脚手架是指通过各种工具来生成项目基础代码的技术。通过脚手架工具生成后的代码,通常已包含了项目开发流程中所需的<strong>工作目录内的通用基础设施</strong>,使开发者可以方便地将注意力集中到业务开发本身。</p>
|
||
<p>那么对于日常的前端开发流程来说,项目内究竟有哪些部分属于通用基础设施呢?让我们从项目创建的流程说起。对于一个前端项目来说,一般在进入开发之前我们需要做的准备有:</p>
|
||
<ol>
|
||
<li>首先我们需要有 <strong>package.json</strong>,它是 npm 依赖管理体系下的基础配置文件。</li>
|
||
<li>然后<strong>选择使用 npm 或 Yarn 作为包管理器</strong>,这会在项目里添加上对应的 <strong>lock 文件</strong>,来确保在不同环境下部署项目时的依赖稳定性。</li>
|
||
<li><strong>确定项目技术栈</strong>,团队习惯的技术框架是哪种?使用哪一种数据流模块?是否使用 TypeScript?使用哪种 CSS 预处理器?等等。在明确选择后安装相关依赖包并在 <strong>src</strong> 目录中建立入口源码文件。</li>
|
||
<li><strong>选择构建工具</strong>,目前来说,构建工具的主流选择还是 webpack (除非项目已先锋性地考虑尝试 nobundle 方案),对应项目里就需要增加相关的 <strong>webpack 配置文件</strong>,可以考虑针对开发/生产环境使用不同配置文件。</li>
|
||
<li><strong>打通构建流程</strong>,通过安装与配置各种 <strong>Loader 、插件和其他配置项</strong>,来确保开发和生产环境能正常构建代码和预览效果。</li>
|
||
<li><strong>优化构建流程</strong>,针对开发/生产环境的不同特点进行各自优化。例如,开发环境更关注构建效率和调试体验,而生产环境更关注访问性能等。</li>
|
||
<li><strong>选择和调试辅助工具</strong>,例如代码检查工具和单元测试工具,安装相应依赖并调试配置文件。</li>
|
||
<li>最后是<strong>收尾工作</strong>,检查各主要环节的脚本是否工作正常,编写说明文档 README.md,将不需要纳入版本管理的文件目录记入 .gitignore 等。</li>
|
||
</ol>
|
||
<p>正如下面简单的示例项目模板,经历了上面这些步骤后我们的项目目录下就新增了这些相关的文件:</p>
|
||
<pre><code>package.json 1) npm 项目文件
|
||
|
||
package-lock.json 2) npm 依赖 lock 文件
|
||
|
||
public/ 3) 预设的静态目录
|
||
|
||
src/ 3) 源代码目录
|
||
|
||
main.ts 3) 源代码中的初始入口文件
|
||
|
||
router.ts 3) 源代码中的路由文件
|
||
|
||
store/ 3) 源代码中的数据流模块目录
|
||
|
||
webpack/ 4) webpack 配置目录
|
||
|
||
common.config.js 4) webpack 通用配置文件
|
||
|
||
dev.config.js 4) webpack 开发环境配置文件
|
||
|
||
prod.config.js 4) webpack 生产环境配置文件
|
||
|
||
.browserlistrc 5) 浏览器兼容描述 browserlist 配置文件
|
||
|
||
babel.config.js 5) ES 转换工具 babel 配置文件
|
||
|
||
tsconfig.json 5) TypeScript 配置文件
|
||
|
||
postcss.config.js 5) CSS 后处理工具 postcss 配置文件
|
||
|
||
.eslintrc 7) 代码检查工具 eslint 配置文件
|
||
|
||
jest.config.js 7) 单元测试工具 jest 配置文件
|
||
|
||
.gitignore 8) Git 忽略配置文件
|
||
|
||
README.md 8) 默认文档文件
|
||
</code></pre>
|
||
<p>而通过脚手架工具,我们就能免去人工处理上的环节,轻松地搭建起项目的初始环境,直接进入到业务开发中。接下来我们就先来看一下前端领域的几个典型脚手架工具,了解这几个脚手架所代表的不同设计理念,接着我们会重点分析两个代表性脚手架工具包内的技术细节,以便在工作中更能得心应手地使用和优化。</p>
|
||
<h3>三种代表性的前端脚手架工具</h3>
|
||
<p><img src="assets/CgqCHl8xA46AOLMIAABL15AXwak581.png" alt="7.png" /></p>
|
||
<h4>Yeoman</h4>
|
||
<p><img src="assets/Ciqc1F8xA0KAKf0uAABJG0oh-Qs463.png" alt="6.png" /></p>
|
||
<p>[图:logo-yeoman]</p>
|
||
<p><a href="https://yeoman.io/">Yeoman</a> 是前端领域内较早出现的脚手架工具,它由 Google I/O 在 2012 年首次发布。Yeoman 提供了基于特定生成器(Generator)来创建项目基础代码的功能。时至今日,在它的网站中能找到超过 5600 个不同技术栈的代码生成器。</p>
|
||
<p>作为早期出现在前端领域的脚手架工具,它没有限定具体的开发技术栈,提供了足够的开放性和自由度,但也因此缺乏某一技术栈的深度集成和技术生态。随着前端技术栈的日趋复杂化,人们更倾向于选择那些以具体技术栈为根本的脚手架工具,而 Yeoman 则更多用于一些开发流程里特定片段代码的生成。</p>
|
||
<h4>Create-React-App</h4>
|
||
<p><img src="assets/CgqCHl8xAqOAAmQFAAAlZny__YI029.png" alt="4.png" /></p>
|
||
<p>[图:logo-create-react-app]</p>
|
||
<p><a href="https://create-react-app.dev/">Create React App</a>(后简称 CRA )是 Facebook 官方提供的 React 开发工具集。它包含了 create-react-app 和 react-scripts 两个基础包。其中 create-react-app 用于选择脚手架创建项目,而 react-scripts 则作为所创建项目中的运行时依赖包,提供了封装后的项目启动、编译、测试等基础工具。</p>
|
||
<p>正如官方网站中所说的,CRA 带来的最大的改变,是将一个项目开发运行时的各种配置细节完全封装在了一个 react-scripts 依赖包中,这大大降低了开发者,尤其是对 webpack 等构建工具不太熟悉的开发者上手开发项目的学习成本,也降低了开发者自行管理各配置依赖包的版本所需的额外测试成本。</p>
|
||
<p>但事情总有两面性,这种近乎黑盒的封装在初期带来便利的同时,也为后期的用户自定义优化带来了困难。虽然官方也提供了 eject 选项来将全部配置注入回项目,但大部分情况下,为了少量优化需求而放弃官方提供的各依赖包稳定升级的便利性,也仍不是一个好的选择。在这种矛盾之下,在保持原有特性的情况下提供自定义配置能力的工具 <a href="https://github.com/timarney/react-app-rewired/">react-rewired</a> 和 <a href="https://github.com/arackaf/customize-cra">customize-cra</a> 应运而生。</p>
|
||
<h4>Vue CLI</h4>
|
||
<p><img src="assets/CgqCHl8xAyuASVwWAAAcGi1IGPY858.png" alt="5.png" /></p>
|
||
<p>[图:logo-vue-cli]</p>
|
||
<p>正如 Create-React-App 在 React 项目开发中的地位, Vue 项目的开发者也有着自己的基础开发工具。Vue CLI 由 Vue.js 官方维护,其定位是 Vue.js 快速开发的完整系统。完整的 Vue CLI 由三部分组成:作为全局命令的 @vue/cli、作为项目内集成工具的 @vue/cli-service、作为功能插件系统的 @vue/cli-plugin-。</p>
|
||
<p>Vue CLI 工具在设计上吸取了 CRA 工具的教训,在保留了创建项目开箱即用的优点的同时,提供了用于覆盖修改原有配置的自定义构建配置文件和其他工具配置文件。</p>
|
||
<p>同时,在创建项目的流程中,Vue CLI 也提供了通过用户交互自行选择的一些定制化选项,例如是否集成路由、TypeScript 等,使开发者更有可能依据这些选项来生成更适合自己的初始化项目,降低了开发者寻找模板或单独配置的成本。</p>
|
||
<p>除了技术栈本身的区别之外,以上三种脚手架工具,实际上代表了三种不同的工具设计理念:</p>
|
||
<ul>
|
||
<li>Yeoman 代表的是一般开源工具的理念。它不提供某一技术栈的最佳实践方案,而专注于实现脚手架生成器的逻辑和提供展示第三方生成器。作为基础工具,它的主要目标群体是生成器的开发者,而非那些需要使用生成器来开发项目的人员,尽管后者也能通过前者的实践而受益。</li>
|
||
<li>CRA 代表的是面向某一技术栈降低开发复杂度的理念。它通过提供一个包含各开发工具的集成工具集和标准化的开发-构建-测试三步流程脚本,使得开发者能无障碍地按照既定流程进行 React 项目的开发和部署。</li>
|
||
<li>Vue CLI 代表的是更灵活折中的理念。它一方面继承了 CRA 降低配置复杂度的优点,另一方面在创建项目的过程中提供了更多交互式选项来配置技术栈的细节,同时允许在项目中使用自定义配置。这样的设计在一定程度上增加了模板维护的复杂度,但是从最终效果来看,能够较大程度满足各类开发者的不同需求。</li>
|
||
</ul>
|
||
<h3>了解脚手架模板中的技术细节</h3>
|
||
<p>刚上手开发项目时,我们通过上述脚手架提供的开箱即用的能力可以很容易地上手开发项目,但是往往在开发过程中遇到问题时又需要回过头来查询文档,看脚手架中是否已有相应解决方案。而<strong>如果我们对该脚手架足够熟悉</strong>,就能<strong>减少这类情况下所花费的时间</strong>,<strong>提升开发效率</strong>。所以在这里,我们先来聊一下该如何了解一个脚手架。</p>
|
||
<p>要了解一个脚手架,除了学会如何使用脚手架来创建项目外,我们还需要了解它提供的<strong>具体功能边界</strong>,提供了<strong>哪些功能、哪些优化</strong>。这样我们才能在后续的开发中更得心应手,后续的优化也更有的放矢。</p>
|
||
<p>还是以上面的 CRA 和 Vue CLI 为例,除了通过脚手架模板生成项目之外,项目内部分别使用 react-scripts 和 vue-cli-service 作为开发流程的集成工具。接下来,我们先来对比下这两个工具在开发与生产环境命令中都使用了哪些配置项,其中一些涉及效率的优化项在后面的课程中还会详细介绍。</p>
|
||
<h4>webpack loaders</h4>
|
||
<p>从下面表格中我们可以发现,在一般源文件的处理器使用方面,两个脚手架工具大同小异,对于 babel-loader 都采用了缓存优化,Vue 中还增加了多线程的支持。在样式和其他类型文件的处理上 Vue 默认支持更多的文件类型,相应的,在 CRA 模板下如果需要支持对应文件就需要使用 customize-cra 等工具来添加新处理模块。</p>
|
||
<p><img src="assets/CgqCHl8w_FmAFzFAAAC4LtmVvTE237.png" alt="1.png" /></p>
|
||
<h4>webpack plugins</h4>
|
||
<p>在与构建核心功能相关的方面(html、env、hot、css extract、fast ts check),两者使用的插件相同,而在其他一些细节功能上各有侧重,例如 React 的 inline chunk 和 Vue 的 preload。</p>
|
||
<p><img src="assets/CgqCHl8w_GeAFNlqAAFvtG9_RV8768.png" alt="2.png" /></p>
|
||
<p>(<a href="https://dutzi.party/react-fast-refresh/">第三方工具</a>)</p>
|
||
<h4>webpack.optimize</h4>
|
||
<p>两者在代码优化配置中相同的部分包括:都使用 TerserPlugin 压缩JavaScript, 都使用 splitChunks 做自动分包 (参数不同)。CSS 的压缩分别采用上面表格中的 OptimizeCssAssetsWebpackPlugin 和 OptimizeCssNanoPlugin 。react-scripts 中还开启了 runtimeChunk 以优化缓存。</p>
|
||
<h4>webpack resolve</h4>
|
||
<p>在 resolve 和 resolve loader 部分,值得一提的是两者都使用 <a href="https://www.npmjs.com/package/pnp-webpack-plugin">PnpWebpackPlugin</a>(pnp) 来加速使用 Yarn 作为包管理器时的模块安装和解析,感兴趣的同学可以 <a href="https://classic.yarnpkg.com/en/docs/pnp/">进一步了解</a>,我们在后面构建和部署的篇章中也会再次谈到。</p>
|
||
<p>通过上述几方面的对比,我们就对这两个典型脚手架工具提供的构建集成能力有了一个大概的了解。这有助于我们在使用具体工具时快速定位问题的边界,同时在使用其他脚手架工具和模板时,我们也可以参照和借鉴上面的最佳实践方案。下一步,我们再来讨论定制专属脚手架模板的问题。</p>
|
||
<h3>如何定制一个脚手架模板</h3>
|
||
<p>虽然官方提供的默认脚手架模板已经代表了对应技术栈的通用最佳实践,但是在实际开发中,我们还是时常需要对通过这些脚手架创建的模板项目进行定制化,例如:</p>
|
||
<ol>
|
||
<li>为项目引入新的通用特性。</li>
|
||
<li>针对构建环节的 webpack 配置优化,来提升开发环境的效率和生产环境的性能等。</li>
|
||
<li>定制符合团队内部规范的代码检测规则配置。</li>
|
||
<li>定制单元测试等辅助工具模块的配置项。</li>
|
||
<li>定制符合团队内部规范的目录结构与通用业务模块,例如业务组件库、辅助工具类、页面模板等。</li>
|
||
</ol>
|
||
<p>通过将这些实际项目开发中所需要做的定制化修改输出为标准的脚手架模板,我们就能在团队内部孵化出更符合团队开发规范的开发流程。一方面最大程度减少大家在开发中处理重复事务的时间,另一方面也能减少因为开发风格不一导致的团队内项目维护成本的增加。接下来,我们就结合上面提到的三个脚手架工具来分别看下如何定制专属的脚手架模板。</p>
|
||
<h4>使用 Yeoman 创建生成器</h4>
|
||
<p>脚手架模板在 Yeoman 中对应的是生成器 (Generator)。作为主打自由制作和分享脚手架生成器的开源工具, Yeoman 为制作生成器提供了丰富的 API 和 <a href="https://yeoman.io/authoring/index.html">详细的文档</a>。在这里,我们简单概述一下,一个基本的复制已有项目模板的生成器包含了:</p>
|
||
<ol>
|
||
<li>生成器描述文件 <strong>package.json</strong>,其中限定了 name、file、keywords 等对应字段的规范赋值。</li>
|
||
<li>作为主体的 <strong>generators/app</strong> 目录,包含生成器的核心文件。该目录是执行 yo 命令时的默认查找目录, Yeoman 支持多目录的方式集成多个子生成器,篇幅原因我就不在这里展开了。</li>
|
||
<li><strong>app/index.js</strong> 是生成器的核心控制模块,其内容是导出一个继承自 yeoman-generator 的类,并由后者提供运行时上下文、用户交互、生成器组合等功能。</li>
|
||
<li><strong>app/templates/</strong> 目录是我们需要复制到新项目中的脚手架模板目录。</li>
|
||
</ol>
|
||
<p>基本目录结构如下所示:</p>
|
||
<pre><code>generator-[name]/
|
||
|
||
package.json
|
||
|
||
generators/
|
||
|
||
app/
|
||
|
||
templates/...
|
||
|
||
index.js
|
||
</code></pre>
|
||
<p>其中 app/index.js 的核心逻辑如下:</p>
|
||
<pre><code>var Generator = require('yeoman-generator')
|
||
|
||
module.exports = class extends Generator {
|
||
|
||
writing() {
|
||
|
||
this.fs.copyTpl(
|
||
|
||
this.templatePath('.'),
|
||
|
||
this.destinationPath('.'))
|
||
|
||
}
|
||
|
||
install() {
|
||
|
||
this.npmInstall()
|
||
|
||
}
|
||
|
||
}
|
||
</code></pre>
|
||
<p>writing 和 install 是 Yeoman 运行时上下文的两个阶段,在例子中,当我们执行下面的创建项目命令时,依次将生成器中模板目录内的所有文件复制到创建目录下,然后执行安装依赖。</p>
|
||
<p>在完成生成器的基本功能后,我们就可以通过在生成器目录里 npm link ,将对应生成器包挂载到全局依赖下,然后进入待创建项目的目录中,执行 yo 创建命令即可。 (如需远程安装,则需要先将生成器包发布到 npm 仓库中,支持发布到 @scope/generator-[name] 。)</p>
|
||
<p><img src="assets/Ciqc1F8w7aaASHMtAABB2xCfKLM444.png" alt="Drawing 4.png" /></p>
|
||
<p>至此,制作 Yeoman 的生成器来定制项目模板的基本功能就完成了。除了基本的复制文件和安装依赖外, Yeoman 还提供了很多实用的功能,例如编写用户交互提示框或合成其他生成器等,可供开发者定制功能体验更完善的脚手架生成器。</p>
|
||
<h4>为 create-react-app 创建自定义模板</h4>
|
||
<p>为 create-react-app 准备的自定义模板在模式上较为简单。作为一个最简化的 CRA 模板,模板中包含如下必要文件:</p>
|
||
<ul>
|
||
<li>README.md:用于在 npm 仓库中显示的模板说明。</li>
|
||
<li>package.json:用于描述模板本身的元信息 (例如名称、运行脚本、依赖包名和版本等) 。</li>
|
||
<li>template.json:用于描述基于模板创建的项目中的 package.json 信息。</li>
|
||
<li>template 目录:用于复制到创建后的项目中,其中 gitignore 在复制后重命名为 .gitignore , public/index.html和src/index 为运行 react-scripts 的必要文件。</li>
|
||
</ul>
|
||
<p>具体目录结构如下所示:</p>
|
||
<pre><code>cra-template-[template-name]/
|
||
|
||
README.md (for npm)
|
||
|
||
template.json
|
||
|
||
package.json
|
||
|
||
template/
|
||
|
||
README.md (for projects created from this template)
|
||
|
||
gitignore
|
||
|
||
public/
|
||
|
||
index.html
|
||
|
||
src/
|
||
|
||
index.js (or index.tsx)
|
||
</code></pre>
|
||
<p>在使用时,同样还是需要将模板通过 npm link 命令映射到全局依赖中,或发布到 npm 仓库中,然后执行创建项目的命令。</p>
|
||
<pre><code>npx create-react-app [app-name] --template [template-name]
|
||
</code></pre>
|
||
<h4>为 Vue CLI 创建自定义模板</h4>
|
||
<p>相比 CRA 模板而言,Vue 的模板中变化最大的当属增加了 meta.js/json 文件,用于描述创建过程中的用户交互信息以及用户选项对于模板文件的过滤等。</p>
|
||
<pre><code>[template-name]/
|
||
|
||
README.md (for npm)
|
||
|
||
meta.js or meta.json
|
||
|
||
template/
|
||
</code></pre>
|
||
<p>此外,Vue 的 template 目录中包含了复制到项目中的所有文件,并且在相关文件中还增加了 handlebars 条件判断的部分,根据 meta.js 中指定用户交互结果选项来将模板中带条件的文件转换为最终生成到项目中的产物。如以下代码所示:</p>
|
||
<pre><code>template/package.json
|
||
|
||
...
|
||
|
||
"dependencies": {
|
||
|
||
"vue": "^2.5.2"{{#router}},
|
||
|
||
"vue-router": "^3.0.1"{{/router}}
|
||
|
||
},
|
||
|
||
...
|
||
|
||
meta.js
|
||
|
||
...
|
||
|
||
prompts: {
|
||
|
||
...
|
||
|
||
router: {
|
||
|
||
when: 'isNotTest',
|
||
|
||
type: 'confirm',
|
||
|
||
message: 'Install vue-router?',
|
||
|
||
},
|
||
|
||
...
|
||
|
||
}
|
||
</code></pre>
|
||
<p>使用自定义模板创建项目的命令为:</p>
|
||
<pre><code>npm install -g @vue/cli-init
|
||
|
||
vue init [template-name] [app-name]
|
||
</code></pre>
|
||
<p>这样就完成了脚手架的定制工作。有了定制化后的脚手架,我们就可以在之后的创建项目时直接进入到业务逻辑的开发中,而不必重复地对官方提供的标准化模板进行二次优化。</p>
|
||
<h3>总结</h3>
|
||
<p>使用脚手架工具是提升开发效率的第一项内容。通过今天的学习,我们了解了脚手架的使用场景,了解了 3 个典型脚手架工具的特点,并分析了 React 和 Vue 官方提供的脚手架工具中的构建集成技术细节。最后,对于希望将业务中使用的更具定制化的基础代码转变为新的脚手架模板的同学,我们也了解了如何在不同工具环境下创建和使用自定义模板。</p>
|
||
<p><strong>课程最后,我想请你来回想一下</strong>:你在项目开发中使用的是哪一种脚手架工具和模板?使用的理由是?你可以将答案写在留言区与大家一起讨论。</p>
|
||
<p>下个课时我们将要学习的是一个大家一直在使用但是很少了解其中细节的技术点:热更新技术。</p>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<div style="float: left">
|
||
<a href="/专栏/前端工程化精讲-完/00 开篇词 建立上帝视角,全面系统掌握前端效率工程化.md.html">上一页</a>
|
||
</div>
|
||
<div style="float: right">
|
||
<a href="/专栏/前端工程化精讲-完/02 界面调试:热更新技术如何开着飞机修引擎?.md.html">下一页</a>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
|
||
</div>
|
||
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"709977475f6f3cfa","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
|
||
</body>
|
||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
|
||
<script>
|
||
window.dataLayer = window.dataLayer || [];
|
||
|
||
function gtag() {
|
||
dataLayer.push(arguments);
|
||
}
|
||
|
||
gtag('js', new Date());
|
||
gtag('config', 'G-NPSEEVD756');
|
||
var path = window.location.pathname
|
||
var cookie = getCookie("lastPath");
|
||
console.log(path)
|
||
if (path.replace("/", "") === "") {
|
||
if (cookie.replace("/", "") !== "") {
|
||
console.log(cookie)
|
||
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
|
||
}
|
||
} else {
|
||
setCookie("lastPath", path)
|
||
}
|
||
|
||
function setCookie(cname, cvalue) {
|
||
var d = new Date();
|
||
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
|
||
var expires = "expires=" + d.toGMTString();
|
||
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
|
||
}
|
||
|
||
function getCookie(cname) {
|
||
var name = cname + "=";
|
||
var ca = document.cookie.split(';');
|
||
for (var i = 0; i < ca.length; i++) {
|
||
var c = ca[i].trim();
|
||
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
|
||
}
|
||
return "";
|
||
}
|
||
|
||
</script>
|
||
|
||
</html>
|