Vue 团队公开快如闪电的全新脚手架工具,未来将替代 Vue-CLI,才300余行代码,学它!...

1. 前言

大家好,我是若川。欢迎关注我的公众号若川视野源码共读活动ruochuan12

想学源码,极力推荐之前我写的《学习源码整体架构系列》jQueryunderscorelodashvuexsentryaxiosreduxkoavue-devtoolsvuex4koa-composevue-next-releasevue-this等十余篇源码文章。

美国时间 2021 年 10 月 7 日早晨,Vue 团队等主要贡献者举办了一个 Vue Contributor Days 在线会议,蒋豪群[1](知乎胖茶[2],Vue.js 官方团队成员,Vue-CLI 核心开发),在会上公开了`create-vue`[3],一个全新的脚手架工具。

create-vue使用npm init vue@next一行命令,就能快如闪电般初始化好基于viteVue3项目。

本文就是通过调试和大家一起学习这个300余行的源码。

阅读本文,你将学到:

1. 学会全新的官方脚手架工具 create-vue 的使用和原理
2. 学会使用 VSCode 直接打开 github 项目
3. 学会使用测试用例调试源码
4. 学以致用,为公司初始化项目写脚手架工具。
5. 等等

2. 使用 npm init vue@next 初始化 vue3 项目

create-vue github README[4]上写着,An easy way to start a Vue project。一种简单的初始化vue项目的方式。

npm init vue@next

估计大多数读者,第一反应是这样竟然也可以,这么简单快捷?

忍不住想动手在控制台输出命令,我在终端试过,见下图。

ff34aafecca8451782b3d485ea71f8c3.png
npm init vue@next

最终cd vue3-projectnpm installnpm run dev打开页面http://localhost:3000[5]

0cacb1d47e454fe0a2faac6ef3d40468.png
初始化页面

2.1 npm init && npx

为啥 npm init 也可以直接初始化一个项目,带着疑问,我们翻看 npm 文档。

npm init[6]

npm init 用法:

npm init [--force|-f|--yes|-y|--scope]
npm init <@scope> (same as `npx <@scope>/create`)
npm init [<@scope>/]<name> (same as `npx [<@scope>/]create-<name>`)

npm init <initializer> 时转换成npx命令:

  • npm init foo -> npx create-foo

  • npm init @usr/foo -> npx @usr/create-foo

  • npm init @usr -> npx @usr/create

看完文档,我们也就理解了:

# 运行
npm init vue@next
# 相当于
npx create-vue@next

我们可以在这里create-vue[7],找到一些信息。或者在npm create-vue[8]找到版本等信息。

其中@next是指定版本,通过npm dist-tag ls create-vue命令可以看出,next版本目前对应的是3.0.0-beta.6

npm dist-tag ls create-vue
- latest: 3.0.0-beta.6
- next: 3.0.0-beta.6

发布时 npm publish --tag next 这种写法指定 tag。默认标签是latest

可能有读者对 npx 不熟悉,这时找到阮一峰老师博客 npx 介绍[9]、nodejs.cn npx[10]

npx 是一个非常强大的命令,从 npm 的 5.2 版本(发布于 2017 年 7 月)开始可用。

简单说下容易忽略且常用的场景,npx有点类似小程序提出的随用随走。

轻松地运行本地命令

node_modules/.bin/vite -v
# vite/2.6.5 linux-x64 node-v14.16.0# 等同于
# package.json script: "vite -v"
# npm run vitenpx vite -v
# vite/2.6.5 linux-x64 node-v14.16.0

使用不同的 Node.js 版本运行代码某些场景下可以临时切换 node 版本,有时比 nvm 包管理方便些。

npx node@14 -v
# v14.18.0npx -p node@14 node -v 
# v14.18.0

无需安装的命令执行

# 启动本地静态服务
npx http-server
# 无需全局安装
npx @vue/cli create vue-project
# @vue/cli 相比 npm init vue@next npx create-vue@next 很慢。# 全局安装
npm i -g @vue/cli
vue create vue-project
d0c2c92db8d148cd440835d9214b1ed4.png
npx vue-cli

npm init vue@nextnpx create-vue@next) 快的原因,主要在于依赖少(能不依赖包就不依赖),源码行数少,目前index.js只有300余行。

3. 配置环境调试源码

3.1 克隆 create-vue 项目

本文仓库地址 create-vue-analysis[11],求个star~

# 可以直接克隆我的仓库,我的仓库保留的 create-vue 仓库的 git 记录
git clone https://github.com/lxchuan12/create-vue-analysis.git
cd create-vue-analysis/create-vue
npm i

当然不克隆也可以直接用 VSCode 打开我的仓库。https://open.vscode.dev/lxchuan12/create-vue-analysis

顺带说下:我是怎么保留 create-vue 仓库的 git 记录的。

# 在 github 上新建一个仓库 `create-vue-analysis` 克隆下来
git clone https://github.com/lxchuan12/create-vue-analysis.git
cd create-vue-analysis
git subtree add --prefix=create-vue https://github.com/vuejs/create-vue.git main
# 这样就把 create-vue 文件夹克隆到自己的 git 仓库了。且保留的 git 记录

关于更多 git subtree,可以看Git Subtree 简明使用手册[12]

3.2 package.json 分析

// create-vue/package.json
{"name": "create-vue","version": "3.0.0-beta.6","description": "An easy way to start a Vue project","type": "module","bin": {"create-vue": "outfile.cjs"},
}

bin指定可执行脚本。也就是我们可以使用 npx create-vue 的原因。

outfile.cjs 是打包输出的JS文件

{"scripts": {"build": "esbuild --bundle index.js --format=cjs --platform=node --outfile=outfile.cjs","snapshot": "node snapshot.js","pretest": "run-s build snapshot","test": "node test.js"},
}

执行 npm run test 时,会先执行钩子函数 pretestrun-s 是 npm-run-all[13] 提供的命令。run-s build snapshot 命令相当于 npm run build && npm run snapshot

根据脚本提示,我们来看 snapshot.js 文件。

3.3 生成快照 snapshot.js

这个文件主要作用是根据const featureFlags = ['typescript', 'jsx', 'router', 'vuex', 'with-tests'] 组合生成31种加上 default 共计 32种 组合,生成快照在 playground目录。

因为打包生成的 outfile.cjs 代码有做一些处理,不方便调试,我们可以修改为index.js便于调试。

// 路径 create-vue/snapshot.js
const bin = path.resolve(__dirname, './outfile.cjs')
// 改成 index.js 便于调试
const bin = path.resolve(__dirname, './index.js')

我们可以在forcreateProjectWithFeatureFlags 打上断点。

createProjectWithFeatureFlags其实类似在终端输入如下执行这样的命令

node ./index.js --xxx --xxx --force
function createProjectWithFeatureFlags(flags) {const projectName = flags.join('-')console.log(`Creating project ${projectName}`)const { status } = spawnSync('node',[bin, projectName, ...flags.map((flag) => `--${flag}`), '--force'],{cwd: playgroundDir,stdio: ['pipe', 'pipe', 'inherit']})if (status !== 0) {process.exit(status)}
}// 路径 create-vue/snapshot.js
for (const flags of flagCombinations) {createProjectWithFeatureFlags(flags)
}

调试VSCode打开项目,VSCode高版本(1.50+)可以在 create-vue/package.json => scripts => "test": "node test.js"。鼠标悬停在test上会有调试脚本提示,选择调试脚本。如果对调试不熟悉,可以看我之前的文章koa-compose

调试时,大概率你会遇到:create-vue/index.js 文件中,__dirname 报错问题。可以按照如下方法解决。在 import 的语句后,添加如下语句,就能愉快的调试了。

// 路径 create-vue/index.js
// 解决办法和nodejs issues
// https://stackoverflow.com/questions/64383909/dirname-is-not-defined-in-node-14-version
// https://github.com/nodejs/help/issues/2907import { fileURLToPath } from 'url';
import { dirname } from 'path';const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

接着我们调试 index.js 文件,来学习。

4. 调试 index.js 主流程

回顾下上文 npm init vue@next 初始化项目的。

4dfac8627c39d70d8924629dc9152729.png
npm init vue@next

单从初始化项目输出图来看。主要是三个步骤。

1. 输入项目名称,默认值是 vue-project
2. 询问一些配置 渲染模板等
3. 完成创建项目,输出运行提示
async function init() {// 省略放在后文详细讲述
}// async 函数返回的是Promise 可以用 catch 报错
init().catch((e) => {console.error(e)
})

4.1 解析命令行参数

// 返回运行当前脚本的工作目录的路径。
const cwd = process.cwd()
// possible options:
// --default
// --typescript / --ts
// --jsx
// --router / --vue-router
// --vuex
// --with-tests / --tests / --cypress
// --force (for force overwriting)
const argv = minimist(process.argv.slice(2), {alias: {typescript: ['ts'],'with-tests': ['tests', 'cypress'],router: ['vue-router']},// all arguments are treated as booleansboolean: true
})

minimist[14]

简单说,这个库,就是解析命令行参数的。看例子,我们比较容易看懂传参和解析结果。

$ node example/parse.js -a beep -b boop
{ _: [], a: 'beep', b: 'boop' }$ node example/parse.js -x 3 -y 4 -n5 -abc --beep=boop foo bar baz
{ _: [ 'foo', 'bar', 'baz' ],x: 3,y: 4,n: 5,a: true,b: true,c: true,beep: 'boop' }

比如

npm init vue@next --vuex --force

4.2 如果设置了 feature flags 跳过 prompts 询问

这种写法方便代码测试等。直接跳过交互式询问,同时也可以省时间。

// if any of the feature flags is set, we would skip the feature prompts// use `??` instead of `||` once we drop Node.js 12 supportconst isFeatureFlagsUsed =typeof (argv.default || argv.ts || argv.jsx || argv.router || argv.vuex || argv.tests) ==='boolean'// 生成目录let targetDir = argv._[0]// 默认 vue-projectsconst defaultProjectName = !targetDir ? 'vue-project' : targetDir// 强制重写文件夹,当同名文件夹存在时const forceOverwrite = argv.force

4.3 交互式询问一些配置

如上文npm init vue@next 初始化的图示

  • 输入项目名称

  • 还有是否删除已经存在的同名目录

  • 询问使用需要 JSX Router vuex cypress 等。

let result = {}try {// Prompts:// - Project name://   - whether to overwrite the existing directory or not?//   - enter a valid package name for package.json// - Project language: JavaScript / TypeScript// - Add JSX Support?// - Install Vue Router for SPA development?// - Install Vuex for state management? (TODO)// - Add Cypress for testing?result = await prompts([{name: 'projectName',type: targetDir ? null : 'text',message: 'Project name:',initial: defaultProjectName,onState: (state) => (targetDir = String(state.value).trim() || defaultProjectName)},// 省略若干配置{name: 'needsTests',type: () => (isFeatureFlagsUsed ? null : 'toggle'),message: 'Add Cypress for testing?',initial: false,active: 'Yes',inactive: 'No'}],{onCancel: () => {throw new Error(red('✖') + ' Operation cancelled')}}])} catch (cancelled) {console.log(cancelled.message)// 退出当前进程。process.exit(1)}

4.4 初始化询问用户给到的参数,同时也会给到默认值

// `initial` won't take effect if the prompt type is null// so we still have to assign the default values hereconst {packageName = toValidPackageName(defaultProjectName),shouldOverwrite,needsJsx = argv.jsx,needsTypeScript = argv.typescript,needsRouter = argv.router,needsVuex = argv.vuex,needsTests = argv.tests} = resultconst root = path.join(cwd, targetDir)// 如果需要强制重写,清空文件夹if (shouldOverwrite) {emptyDir(root)// 如果不存在文件夹,则创建} else if (!fs.existsSync(root)) {fs.mkdirSync(root)}// 脚手架项目目录console.log(`\nScaffolding project in ${root}...`)// 生成 package.json 文件const pkg = { name: packageName, version: '0.0.0' }fs.writeFileSync(path.resolve(root, 'package.json'), JSON.stringify(pkg, null, 2))

4.5 根据模板文件生成初始化项目所需文件

// todo:// work around the esbuild issue that `import.meta.url` cannot be correctly transpiled// when bundling for node and the format is cjs// const templateRoot = new URL('./template', import.meta.url).pathnameconst templateRoot = path.resolve(__dirname, 'template')const render = function render(templateName) {const templateDir = path.resolve(templateRoot, templateName)renderTemplate(templateDir, root)}// Render base templaterender('base')// 添加配置// Add configs.if (needsJsx) {render('config/jsx')}if (needsRouter) {render('config/router')}if (needsVuex) {render('config/vuex')}if (needsTests) {render('config/cypress')}if (needsTypeScript) {render('config/typescript')}

4.6 渲染生成代码模板

// Render code template.// prettier-ignoreconst codeTemplate =(needsTypeScript ? 'typescript-' : '') +(needsRouter ? 'router' : 'default')render(`code/${codeTemplate}`)// Render entry file (main.js/ts).if (needsVuex && needsRouter) {render('entry/vuex-and-router')} else if (needsVuex) {render('entry/vuex')} else if (needsRouter) {render('entry/router')} else {render('entry/default')}

4.7 如果配置了需要 ts

重命名所有的 .js 文件改成 .ts。重命名 jsconfig.json 文件为 tsconfig.json 文件。

jsconfig.json[15] 是VSCode的配置文件,可用于配置跳转等。

index.html 文件里的 main.js 重命名为 main.ts

// Cleanup.if (needsTypeScript) {// rename all `.js` files to `.ts`// rename jsconfig.json to tsconfig.jsonpreOrderDirectoryTraverse(root,() => {},(filepath) => {if (filepath.endsWith('.js')) {fs.renameSync(filepath, filepath.replace(/\.js$/, '.ts'))} else if (path.basename(filepath) === 'jsconfig.json') {fs.renameSync(filepath, filepath.replace(/jsconfig\.json$/, 'tsconfig.json'))}})// Rename entry in `index.html`const indexHtmlPath = path.resolve(root, 'index.html')const indexHtmlContent = fs.readFileSync(indexHtmlPath, 'utf8')fs.writeFileSync(indexHtmlPath, indexHtmlContent.replace('src/main.js', 'src/main.ts'))}

4.8 配置了不需要测试

因为所有的模板都有测试文件,所以不需要测试时,执行删除 cypress/__tests__/ 文件夹

if (!needsTests) {// All templates assumes the need of tests.// If the user doesn't need it:// rm -rf cypress **/__tests__/preOrderDirectoryTraverse(root,(dirpath) => {const dirname = path.basename(dirpath)if (dirname === 'cypress' || dirname === '__tests__') {emptyDir(dirpath)fs.rmdirSync(dirpath)}},() => {})}

4.9 根据使用的 npm / yarn / pnpm 生成README.md 文件,给出运行项目的提示

// Instructions:// Supported package managers: pnpm > yarn > npm// Note: until <https://github.com/pnpm/pnpm/issues/3505> is resolved,// it is not possible to tell if the command is called by `pnpm init`.const packageManager = /pnpm/.test(process.env.npm_execpath)? 'pnpm': /yarn/.test(process.env.npm_execpath)? 'yarn': 'npm'// README generationfs.writeFileSync(path.resolve(root, 'README.md'),generateReadme({projectName: result.projectName || defaultProjectName,packageManager,needsTypeScript,needsTests}))console.log(`\nDone. Now run:\n`)if (root !== cwd) {console.log(`  ${bold(green(`cd ${path.relative(cwd, root)}`))}`)}console.log(`  ${bold(green(getCommand(packageManager, 'install')))}`)console.log(`  ${bold(green(getCommand(packageManager, 'dev')))}`)console.log()

5. npm run test => node test.js 测试

// create-vue/test.js
import fs from 'fs'
import path from 'path'
import { fileURLToPath } from 'url'import { spawnSync } from 'child_process'const __dirname = path.dirname(fileURLToPath(import.meta.url))
const playgroundDir = path.resolve(__dirname, './playground/')for (const projectName of fs.readdirSync(playgroundDir)) {if (projectName.endsWith('with-tests')) {console.log(`Running unit tests in ${projectName}`)const unitTestResult = spawnSync('pnpm', ['test:unit:ci'], {cwd: path.resolve(playgroundDir, projectName),stdio: 'inherit',shell: true})if (unitTestResult.status !== 0) {throw new Error(`Unit tests failed in ${projectName}`)}console.log(`Running e2e tests in ${projectName}`)const e2eTestResult = spawnSync('pnpm', ['test:e2e:ci'], {cwd: path.resolve(playgroundDir, projectName),stdio: 'inherit',shell: true})if (e2eTestResult.status !== 0) {throw new Error(`E2E tests failed in ${projectName}`)}}
}

主要对生成快照时生成的在 playground 32个文件夹,进行如下测试。

pnpm test:unit:cipnpm test:e2e:ci

6. 总结

我们使用了快如闪电般的npm init vue@next,学习npx命令了。学会了其原理。

npm init vue@next => npx create-vue@next

快如闪电的原因在于依赖的很少。很多都是自己来实现。如:Vue-CLIvue create vue-project 命令是用官方的npm包validate-npm-package-name[16],删除文件夹一般都是使用 rimraf[17]。而 create-vue 是自己实现emptyDirisValidPackageName

非常建议读者朋友按照文中方法使用VSCode调试 create-vue 源码。源码中还有很多细节文中由于篇幅有限,未全面展开讲述。

学完本文,可以为自己或者公司创建类似初始化脚手架。

目前版本是3.0.0-beta.6。我们持续关注学习它。除了create-vue 之外,我们还可以看看create-vite[18]、create-umi[19] 的源码实现。

最后欢迎加我微信 ruochuan12源码共读 活动,大家一起学习源码,共同进步。

7. 参考资料

发现 create-vue 时打算写文章加入到源码共读比我先写完文章。

@upupming  vue-cli 将被 create-vue 替代?初始化基于 vite 的 vue3 项目为何如此简单?

参考资料

[1]

点击阅读原文查看更多

最近组建了一个湖南人的前端交流群,如果你是湖南人可以加我微信 ruochuan12 私信 湖南 拉你进群。


推荐阅读

1个月,200+人,一起读了4周源码
我历时3年才写了10余篇源码文章,但收获了100w+阅读

老姚浅谈:怎么学JavaScript?

我在阿里招前端,该怎么帮你(可进面试群)

48ea46ad379c287f6b6f7d3f2ead7785.gif

················· 若川简介 ·················

你好,我是若川,毕业于江西高校。现在是一名前端开发“工程师”。写有《学习源码整体架构系列
从2014年起,每年都会写一篇年度总结,已经写了7篇,点击查看年度总结。
同时,最近组织了源码共读活动

8b19b9b1a24f52305f09f5a006722c43.png

识别方二维码加我微信、拉你进源码共读

今日话题

略。欢迎分享、收藏、点赞、在看我的公众号文章~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/275322.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

斑马无线打印服务器,如何设置斑马打印机无线WiFi

安装Zebra Setup Utilities.exe&#xff0c;打开软件(没有该软件的可以向客服索要)界面如果是英文请选择options(选项)&#xff0c;选择应用程序语言Simplified Chinese(简体中文)点击确定&#xff0c;关闭软件&#xff0c;重新打开&#xff0c;界面就会显示中文。点击相应的打…

Python自然语言处理学习笔记(19):3.3 使用Unicode进行文字处理

3.3 Text Processing with Unicode 使用Unicode进行文字处理 Our programs will often need to deal with different languages, and different character sets. The concept of “plain text” is a fiction&#xff08;虚构&#xff09;. If you live in the English-speakin…

小程序卡片叠层切换卡片_现在,卡片和清单在哪里?

小程序卡片叠层切换卡片重点 (Top highlight)介绍 (Intro) I was recently tasked to redesign the results of the following filters:我最近受命重新设计以下过滤器的结果&#xff1a; Filtered results for users (creatives) 用户的筛选结果(创意) 2. Filtered results fo…

效率神器!UI 稿智能转换成前端代码

做前端&#xff0c;不搬砖大家好&#xff0c;我是若川。从事前端五年之久&#xff0c;也算见证了前端数次变革&#xff0c;从到DW&#xff08;Dreamweaver&#xff09;到H5C3、从JQuery到MVC框架&#xff0c;无数前端大佬在为打造前端完整生态做出努力&#xff0c;由于他们的努…

$.when.apply_When2Meet vs.LettuceMeet:UI和美学方面的案例研究

$.when.apply并非所有计划应用程序都是一样创建的。 (Not all scheduling apps are created equal.) As any college student will tell you, we use When2Meet almost religiously. Between classes, extracurriculars, work, and simply living, When2Meet is the scheduling…

前端不容你亵渎

大家好&#xff0c;我是若川&#xff0c;点此加我微信进源码群&#xff0c;一起学习源码。同时可以进群免费看Vue专场直播&#xff0c;有尤雨溪分享「Vue3 生态现状以及展望」背景最近我在公众号的后台收到一条留言&#xff1a;言语里充满了对前端的不屑和鄙夷&#xff0c;但仔…

利益相关者软件工程_如何向利益相关者解释用户体验的重要性

利益相关者软件工程With the ever increasing popularity of user experience (UX) design there is a growing need for good designers. However, there’s a problem for designers here as well. How can you show the importance of UX to your stakeholders and convince…

云栖大会上,阿里巴巴重磅发布前端知识图谱!

大家好&#xff0c;我是若川&#xff0c;点此加我微信进源码群&#xff0c;一起学习源码。同时可以进群免费看Vue专场直播&#xff0c;有尤雨溪分享「Vue3 生态现状以及展望」阿里巴巴前端知识图谱&#xff0c;由大阿里众多前端技术专家团历经1年时间精心整理&#xff0c;从 初…

Linux下“/”和“~”的区别

在linux中&#xff0c;”/“代表根目录&#xff0c;”~“是代表目录。Linux存储是以挂载的方式&#xff0c;相当于是树状的&#xff0c;源头就是”/“&#xff0c;也就是根目录。 而每个用户都有”家“目录&#xff0c;也就是用户的个人目录&#xff0c;比如root用户的”家“目…

在当今移动互联网时代_谁在提供当今最好的电子邮件体验?

在当今移动互联网时代Hey, a new email service from the makers of Basecamp was recently launched. The Verge calls it a “genuinely original take on messaging”, and it indeed features some refreshing ideas for the sometimes painful exercise we call inbox man…

React 全新文档上线!

大家好&#xff0c;我是若川&#xff0c;点此加我微信进源码群&#xff0c;一起学习源码。同时可以进群免费看明天的Vue专场直播&#xff0c;有尤雨溪分享「Vue3 生态现状以及展望」&#xff0c;还可以领取50场录播视频和PPT。React 官方文档改版耗时 1 年&#xff0c;今天已完…

网络低俗词_从“低俗小说”中汲取7堂课,以创建有影响力的作品集

网络低俗词重点 (Top highlight)Design portfolios and blockbuster movies had become more and more generic. On the design side, I blame all the portfolio reviews and articles shared by “experienced” designers that repeat the same pieces of advice regardless…

尤雨溪写的100多行的“玩具 vite”,十分有助于理解 vite 原理

1. 前言大家好&#xff0c;我是若川。最近组织了源码共读活动&#xff0c;感兴趣的可以加我微信 ruochuan12想学源码&#xff0c;极力推荐之前我写的《学习源码整体架构系列》jQuery、underscore、lodash、vuex、sentry、axios、redux、koa、vue-devtools、vuex4、koa-compose、…

webflow如何使用_我如何使用Webflow构建辅助项目以帮助设计人员进行连接

webflow如何使用I launched Designer Slack Communities a while ago, aiming to help designers to connect with likeminded people. By sharing my website with the world, I’ve connected with so many designers. The whole experience is a first time for me, so I wa…

重新构想原子化 CSS

感谢印记中文的 QC-L[1] 对本文进行翻译&#xff0c;英文原文: English Version[2]。本文会比往期文章相对长些。这是我个人的一个重大的工具发布&#xff0c;有许多内容我想谈论。如果你能花些时间读完&#xff0c;不胜感激&#xff0c;希望能对你有所帮助 :)推荐访问 https:/…

cv::mat 颜色空间_网站设计基础:负空间

cv::mat 颜色空间Let’s start off by answering this question: What is negative space? It is the “empty” space between and around the subjects of an image. In the context of web design, your “subjects” are the pictures, videos, text, buttons and other e…

[知乎回答] 前端是否要学习 Node.js?

大家好&#xff0c;我是若川。最近组织了源码共读活动&#xff0c;感兴趣的可以加我微信 ruochuan12很多小伙伴都表示收获颇丰。一起学的大多数200行左右的Node.js源码。今天推荐这篇文章。&#xff08;刚刚在写明天掘金要发的文章&#xff0c;差点忘记今天还没发文。在知乎上看…

shields 徽标_我的徽标素描过程

shields 徽标Sketching is arguably the most important part of my process when it comes to logo design. In the beginning of my design career, I would actually skip this step completely and go right to the computer. I’d find myself getting stuck and then goi…

叮咚,系统检测到 npm 有更新,原理揭秘!

大家好&#xff0c;我是若川。最近组织了源码共读活动&#xff0c;感兴趣的可以加我微信 ruochuan12本文来自V同学投稿的源码共读第六期笔记&#xff0c;写得很有趣。现在已经进行到第十期了。你或许经常看见 npm 更新的提示。npm 更新提示面试官可能也会问你&#xff0c;组件库…

ui设计未来十年前景_UI设计的10条诫命

ui设计未来十年前景重点 (Top highlight)The year is approximately 1,300 BC when Moses received the 10 UI design commandments from the almighty design gods. The list was comprised of best practices that only the most enlightened designers would be aware of.当…