# imooc-lego **Repository Path**: haokur_demos/imooc-cli-dev ## Basic Information - **Project Name**: imooc-lego - **Description**: 慕课网前端架构师课程笔记。https://class.imooc.com/sale/fearchitect - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 4 - **Created**: 2024-05-15 - **Last Updated**: 2024-05-15 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 介绍 * 流程图:https://whimsical.com/mongodb-RTJphPrwzksyotCdA32LQU * 需求文档:https://imooc-lego.yuque.com/imooc-lego/zlz87z * 作业|学习笔记:https://homework.imooc-lego.com/ * 慕课网:https://class.imooc.com/fearchitect#Anchor `imooc-cli-dev` 为脚手架代码 `imooc-cli-dev-server` 为模板代码的服务端 `lego` 为前端代码 # 项目笔记 ## 第一周:需求分析和架构设计 > 浅层学习看输入,深入学习看输出! ## 第二周:脚手架架构设计和框架搭建 ### 创建脚手架流程 ![image-20230101195520789](https://image.mdashen.com/pic/image-20230101195520789.png) package.json 中添加 `bin:{"imooc-test":"bin/index.js"}` #### 脚手架参数解析 ```javascript const process = require('process') const argv = process.argv ``` ### Lerna #### lerna 搭建脚手架 ```javascript lerna init // 初始化 lerna 项目 lerna create core // 通过 lerna 创建分包 ``` 分包 name 规则:`"name": "@imooc-yishen-cli-dev/core"` ,`@imooc-yishen-cli-dev` 为 `Organizations` 名称;`core` 为分包名称。 #### lerna 本地依赖实现方式 ```javascript "dependencies": { "@imooc-yishen-cli-dev/utils": "file:../utils" } // 执行 lerna publish 时lerna 会帮我们将 file:../utils 提换成线上链接 ``` ## 第三周:脚手架核心流程开发 ### 脚手架架构设计 #### 脚手架架构设计图 ![image-20230105182425298](https://image.mdashen.com/pic/image-20230105182425298.png) #### 脚手架拆包策略 * 核心流程:core * 命令:commands * 初始化 * 发布 * 清除缓存 * 模型车:models * Command 命令 * Project 项目 * Component 组件 * Npm 模块 * Git 仓库 * 支撑模块:utils * Git 操作 * 云构建 * 工具方法 * API 请求 * Git API #### core 模块技术方案 ![image-20230105183200138](https://image.mdashen.com/pic/image-20230105183200138.png) ## 第四周 脚手架命令注册和执行过程开发 ![image-20230110220628643](https://image.mdashen.com/pic/image-20230110220628643.png) execCommand ![image-20230111132743539](https://image.mdashen.com/pic/image-20230111132743539.png) ## 第五周 脚手架创建项目流程设计和开发 项目/组件的初始化 ![image-20230130133002410](https://image.mdashen.com/pic/image-20230130133002410.png) ## 第六周 脚手架项目和组件初始化开发 项目/组件初始化中的安装模板步骤 ![image-20230130095857215](https://image.mdashen.com/pic/image-20230130095857215.png) ### ejs 模板引擎 * https://github.com/mde/ejs #### 基本用法 ```javascript const html = '
<%= user.name %>
' const options = {} const data = { user: { name: 'yishen', blog:'https://mdashen.com' }, } const data2 = { user: { name: 'yishen_2', }, } ``` 1. compile 渲染 ```javascript // compile 相比 渲染变量更消耗资源。所以设计:只需 compile 一次,就可渲染多个template const template = ejs.compile(html, options) const compileTemplate = template(data) const compileTemplate2 = template(data2) console.log(compileTemplate, compileTemplate2) // out
yishen
yishen_2
``` 2. render 渲染 ```javascript ejs.render(html, data, options); // 不需要反复使用 1. 中的template 可直接使用此方法。若需要对 template 反复使用,建议使用第一种方法。性能有提升 ``` 3. 读取文件渲染 ```javascript ejs.renderFile(filename, data, options, function(err, str){ // str => Rendered HTML string }); // filename 为文件路径。 ``` #### 标签 - `<%` 'Scriptlet' tag, for control-flow, no output - `<%_` 'Whitespace Slurping' Scriptlet tag, strips all whitespace before it - `<%=` Outputs the value into the template (escaped) - `<%-` Outputs the unescaped value into the template - `<%#` Comment tag, no execution, no output - `<%%` Outputs a literal '<%' - `%%>` Outputs a literal '%>' - `%>` Plain ending tag - `-%>` Trim-mode ('newline slurp') tag, trims following newline - `_%>` 'Whitespace Slurping' ending tag, removes all whitespace after it ## 第七周 B端项目需求分析和架构设计 流程图:https://whimsical.com/mongodb-RTJphPrwzksyotCdA32LQU 需求:https://imooc-lego.yuque.com/imooc-lego/zlz87z ## 第八周 前端基础技术回顾和巡礼 ### Typescript ```typescript // interface 约束函数 const sum = (x: number, y: number) => { return x + y } interface Isum { (x: number, y: number): number } const sum2: Isum = sum // 可变属性名 interface RandoMap { [propName: string]: string } const test: RandoMap = { a: 'hello', b: 'hello', } ``` ```typescript // interface 约束类 // 实例类型 interface ClockInterface { currentTime: number alert(): void } // 静态类型 interface ClockStatic { // new 表示这个约束可以被new调用(即构造函数) new (h: number, m: number): void } // ClockStatic 用来约束构造函数。ClockInterface用来约束class。静态类型和实例类型共同约束class // implements 关键词 类来实现接口 const Clock: ClockStatic = class Clock implements ClockInterface { currentTime = 0 alert: () => {} constructor(h: number, m: number) {} } interface GameInterface { play(): void } class Cellphone implements ClockInterface, GameInterface { currentTime: number alert() {} play() {} } ``` ```typescript // 泛型 // 第一个T:代表使用泛型 需要使用<>包裹 // 第二个T:代表参数的类型 // 第三个T:代表函数的返回值类型 function echo(arg: T): T { return arg } const numberResult = echo(1) const stringResult = echo('123') ``` ![image-20230403034047131](https://image.mdashen.com/pic/20230403034052.png) ```typescript // TS 操作类型 interface CountryResp { name: string area: number population: number } // keys 联合类型:name || area || population type Keys = keyof CountryResp type NameType = CountryResp['name'] type Test = { [key in Keys]: any } type CountyOpt = { [p in Keys]?: CountryResp[p] } ``` ![image-20230408214216613](https://image.mdashen.com/pic/20230408214222.png) ```typescript // 泛型约束 interface IWidthLength { length: number } // 传入的参数 arg 必须携带有 length 属性 function echoWithArr(arg: T): T { console.log(arg.length) return arg } const arrs = echoWithArr([1, 2, 3]) const str = echoWithArr('123') const obj = echoWithArr({ length: 123 }) ``` ### Vue3 纯函数 pure function * 相同的输入,永远会得到相同的输出 * 没有副作用 watch 遇到非响应式值的情况 ![image-20230410010310385](https://image.mdashen.com/pic/20230410010316.png) ## 第九周 项目整体搭建 ## 第十周 业务组件库初步开发,业务组件属性的展示和更新 jsx写法 https://cn.vuejs.org/guide/extras/render-function.html#jsx-tsx ### tsx ```tsx import { defineComponent } from 'vue' export default defineComponent({ props: { vNode: { type: [Object, String], required: true } }, render() { return this.vNode } }) ``` 相比template sfc,灵活,可以用js来表达各种逻辑,但是可读性差。 结论:**优先选择 template ,当template写起来费劲使用tsx** 核心实现:数据流转方式。**单向数据流** ![image-20240423085743999](https://image.mdashen.com/pic-manjaro/1713833864.png) ## 第十一周 掌握测试基本工具,给组件库添加单元测试 ### 测试框架 Jest 官网:https://jestjs.io/ 测试框架的功能 * 断言 * 异步支持 * Mock * 代码覆盖率 ### jest 基础 当然,以下是一些与 Jest 测试相关的常见名词及其简要介绍: **Test Suite(测试套件)** 一个测试套件包含一组相关的测试用例。在 Jest 中,一个测试套件使用 `describe` 函数定义。 ```javascript javascriptCopy codedescribe('Math operations', () => { // 测试用例 }); ``` **Test Case(测试用例)** 测试用例是具体的单元测试,用于验证代码的某个特定行为。在 Jest 中,使用 `test` 或 `it` 函数定义测试用例。 ```js javascriptCopy codetest('adds 1 + 2 to equal 3', () => { expect(1 + 2).toBe(3); }); ``` * 创建mock function ,在测试中使用,用来测试回调。 * 手动mock,覆盖第三方实现。 ```ts // mock const mockTest = (shouldCall: any, cb: any) => { if (shouldCall) { cb(42) return cb(42) } } it('test with mock function', () => { const mockCb = jest.fn() mockTest(true, mockCb) //期待 mockCb 被调用:断言 expect(mockCb).toHaveBeenCalled() // 校验参数 expect(mockCb).toHaveBeenCalledWith(42) // 调用次数 expect(mockCb).toHaveBeenCalledTimes(2) }) ``` * jest timers mock ```ts // mock timer const loopFetchUser = (cb: any) => { setTimeout(() => { cb('one') setTimeout(() => { cb('two') }, 2000) }, 1000) } // jest 结果事件控制器,setTimeout,setinterval 之类函数会被影响 jest.useFakeTimers() it('test the callback after 1 sec', () => { const callback = jest.fn() loopFetchUser(callback) expect(callback).not.toHaveBeenCalled() jest.runOnlyPendingTimers() expect(callback).toHaveBeenCalled() expect(callback).toHaveBeenCalledTimes(1) expect(callback).toHaveBeenCalledWith('one') jest.runOnlyPendingTimers() expect(callback).toHaveBeenCalledTimes(2) expect(callback).toHaveBeenCalledWith('two') // 前进多少ms // jest.advanceTimersByTime(500) }) ``` #### 测试准备和结束 一次性完成测试准备。可以在这里定义一些每个用例都需要使用的共享状态 * beforeAll * afterAll 每个用例前的测试准备。如果这个用例,不需要beforeAll中的共享,可以使用这个清除共享状态。 * beforeEach * afterEach ### vue test utils > 提供特定的方法,在隔离的环境下,进行组件的挂载,以及一些列的测试。 vite中使用:https://vitest.dev/ https://cn.vuejs.org/guide/scaling-up/testing.html ### 测试内容 ```json "scripts": { "test:unit": "vitest --watch", }, ``` test 命令 :`"test:unit": "vitest --watch"` 结尾处添加--watch 可以监听模式 vscode 可以安装 https://marketplace.visualstudio.com/items?itemName=vitest.explorer **mount和shallowMount区别** * mount全部渲染 * shallowMount 只渲染组件本身,外来子组件不渲染 * shallowMount更快,更适合单元测试 #### mock全局组件 * 方法一,mock实现组件 helloworld.vue ```vue ``` mock用例 ```ts import { shallowMount, mount, VueWrapper } from '@vue/test-utils' import { describe, it, expect, beforeAll, vitest } from 'vitest' import HelloWrold from '../components/HelloWorld.vue' let wrapper: VueWrapper vitest.mock('ant-design-vue') const mockComponent = { template: `
` } const globalComponents = { 'a-button': mockComponent } describe('HelloWrold', () => { beforeAll(() => { wrapper = mount(HelloWrold, { props: { msg: 'msg' }, global: { components: globalComponents } }) }) it('renders props.msg when passed', () => { expect(wrapper.text()).toContain('msg') console.log(wrapper.html()) }) }) ``` * 方法一,mock实现 vue组件 ```vue ``` 用例 ```typescript import { message } from 'ant-design-vue' vitest.mock('ant-design-vue', () => ({ message: { success: vitest.fn() } })) it('message is beCall', async () => { expect(message.success).toBeCalled() expect(message.success).toBeCalledTimes(1) expect(message.success).toBeCalledWith('Hello World!') }) ``` * 方法二:直接使用真实的 ```ts import store from '../stores/editor' beforeAll(() => { wrapper = mount(HelloWrold, { global: { provide: { store } } }) }) ``` ## 第十二周 通用上传组件开发以及使用 #### input.value 属性 ```vue ``` https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#value > A file input's [`value`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#value) attribute contains a string that represents the path to the selected file(s). If no file is selected yet, the value is an empty string (`""`). ![image-20240426163532543](https://image.mdashen.com/pic/image-20240426163532543.png) #### 本地图片预览 > 快速预览本地图片,不需要等待图片上传完成。 * [URL: createObjectURL()](https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL_static) 返回 url 类似 `blob:http://192.168.83.113:5173/65e2ae70-33d7-407e-9019-1d99c98a0595`;得到当前文件的一个内存 URL;多次使用需通过: [`URL.revokeObjectURL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/revokeObjectURL_static) 手动释放内存。 * [FileReader: readAsDataURL()](https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsDataURL) 返回 base64 # 与项目业务无关的一些通用知识点 ### 大厂 git 规范讲解 ![image-20230105163326160](https://image.mdashen.com/pic/image-20230105163326160.png) ### git flow ![image-20230419000534679](https://image.mdashen.com/pic/20230419000540.png) ### require 加载资源类型 ```javascript const pkg = require('../package.json') // require 支持.js 、.json 文件 ``` * js 文件:`module.exports ` / `exports` * json文件:JSON.parse 解析成对象 * .node:c++ 插件,不常用 * 其他任意文件,当做.js 解析,解析失败报错。解析成功就是js文件。 ### NodeJS 支持 ES Module 两种标准 CommonJS、ES Module * CommonJS: require()、modulex.exports / exports.x * ES Module:import、export default / export function/const #### 通过 webpack 支持模块化 `npm i webpack webpack-cli` ```javascript // webpack.config.js const path = require('path') module.exports = { entry: './bin/index.js', mode: 'development', output: { path: path.join(__dirname, '/dist'), filename: 'index.js', }, } ``` ```javascript // package.json "scripts": { "dev": "webpack --watch", "build": "webpack" }, ``` ##### babel 转es5 语法 `npm i babel-loader @babel/core @babel/preset-env` ```javascript // webpack.config.js const path = require('path') module.exports = { entry: './bin/core.js', // mode: 'development', mode: 'production', output: { path: path.join(__dirname, '/dist'), filename: 'index.js', }, target: 'node', module: { rules: [ { test: /\.js$/, exclude: /(node_modules|dist)/, use: { loader: 'babel-loader', options:{ presets:['@babel/preset-env'] } }, }, ], }, } ``` #### 通过 Node 原生支持 ES Module mjs * https://stackoverflow.com/questions/57492546/what-is-the-difference-between-js-and-mjs-files > Node.js's original module system is [CommonJs](https://nodejs.org/docs/latest/api/modules.html) (which uses `require` and `module.exports`). > > Since Node.js was created, the [ECMAScript module system](https://nodejs.org/docs/latest/api/esm.html) (which uses `import` and `export`) has become standard and Node.js has added support for it. > > Node.js will treat `.cjs` files as CommonJS modules and `.mjs` files as ECMAScript modules. It will treat `.js` files as whatever the default module system for the project is (which is CommonJS unless *package.json* says `"type": "module",`). > > See also: [Differences between ES6 module system and CommonJs](https://nodejs.org/docs/latest/api/esm.html#esm_differences_between_es_modules_and_commonjs) ### Node 多进程 * child_process 中创建的进程就是 Node.js 的子进程 * spawn:耗时任务,需要不断日志 * exec/exceFile:开销比较小的任务 ```javascript // 执行命令 cp.exec('ls -al', (error, stdout, stderr) => { console.log('') }) // 执行文件 cp.execFile( path.resolve(__dirname, 'test.shell'), ['-al', '-bl'], (error, stdout, stderr) => { console.log(error) console.log(stdout) console.log(stderr) } ) ``` ```javascript const child = cp.spawn(path.resolve(__dirname, 'test.shell'), ['-al', '-bl'], { cwd: path.resolve('..'), }) child.stdout.on('data', function (chunk) { console.log(chunk.toString()) }) child.stderr.on('data', function (chunk) { console.log(chunk.toString()) }) ``` * fork:创建一个新进程(子),通过 node 执行 ```javascript // main const child = cp.fork(path.resolve(__dirname, 'child.js')) // 父进程像子进程发送消息 child.send('hello child process', () => { // child.disconnect() }) // 父进程监听子进程的发来的消息 child.on('message', (msg) => { console.log(msg) }) console.log('main pid:', process.pid) ``` ```javascript // child console.log('child process') console.log('child pid', process.pid) process.on('message', (msg) => { console.log(msg) }) process.send('hello main process') ``` result ```sh main pid: 2023 child process child pid 2024 hello main process hello child process ``` ### 命令行交互原理 // TODO 视频跳过了,看完整个项目再回来补。 // TODO 第五章、7-x ### TDD **Test Driven Development-测试驱动开发** * 先根据需求写测试用例 * 测试用例全部实现 * 开始写代码实现 * 将测试用例由失败变成通过 **动机** * 在写用例的过程中,梳理编码思路 * 将编程过程任务化,可以对进度做到更加精确的把握 **好处** * 对之前的功能更有信心,不会怕新功能搞坏了老功能 * 重构更方便了 ### vue 三种实例 1. vue2 * 每个 Vue 应用都是 new Vue 函数创建的一个新的实例 * 创建的时候将 data 作为 property 添加到响应式系统中 2. vue3 application instance * createApp 创建一个 **Application Instance** * 应用实例用来注册应用中的全局内容 3. vue3 component instance * createApp ,root component * mount方法 * 将应用实例挂载到 DOM 节点上 * 返回的不是应用实例,二十组件实例(同Vue2) ### vue3 通信方法 ![image-20240508102806891](https://image.mdashen.com/pic/image-20240508102806891.png) 1. 父组件访问子组件实例 https://cn.vuejs.org/guide/essentials/template-refs.html#ref-on-component ```vue ``` 2. 子组件访问父组件实例 ```vue ``` * 直接调用父组件上的方法和函数,这是一种很不好的做法。 * 应该保持单向数据量,子组件发送特定事件去触发父组件的改变。 3. provide 和 inject https://cn.vuejs.org/guide/components/provide-inject.html ![Provide/inject 模式](https://image.mdashen.com/pic/provide-inject.C0gAIfVn.png) ```js import { createApp } from 'vue' const app = createApp({}) app.provide(/* 注入名 */ 'message', /* 值 */ 'hello!') ``` ```vue ``` 4. 事件监听器 在以下特殊的情形,需要使用事件监听器完成父子通信 比如,父组件中有 slot,子组件是以 slot 形式存在,无法添加ref https://www.npmjs.com/package/mitt