整合營(yíng)銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          搭建自己的 typescript 項(xiàng)目 + 開(kāi)發(fā)自己的腳手架工具 ts-cli

          為什么要自己搭建項(xiàng)目 ?

          這個(gè)事情起始于前兩天給團(tuán)隊(duì)寫(xiě)一個(gè)腳手架工具,這段時(shí)間剛做做完一個(gè)系統(tǒng),是團(tuán)隊(duì)第一個(gè)正式意義上的全棧項(xiàng)目;團(tuán)隊(duì)有自己的前端腳手架,但是沒(méi)有后端腳手架,所以想著給團(tuán)隊(duì)寫(xiě)一個(gè)后端腳手架工具,這樣團(tuán)隊(duì)在開(kāi)展后續(xù)的全棧項(xiàng)目時(shí)可以使用腳手架構(gòu)建項(xiàng)目,避免了重復(fù)搭建項(xiàng)目初始架構(gòu)的大量工作,成員直接編寫(xiě)業(yè)務(wù)代碼即可。

          前兩天用 JavaScript 完成了腳手架的初版,開(kāi)發(fā)過(guò)程中發(fā)現(xiàn)沒(méi)一點(diǎn)代碼提示,很不舒服,而且這樣的代碼不利于后續(xù)的迭代和維護(hù)。

          所以決定用 typescript 重構(gòu)一遍,但是官方好像沒(méi)有提供一個(gè)合適的腳手架工具,于是就開(kāi)始自己搭建 typescript 項(xiàng)目;自己搭建最大的好處就是 自主可控,項(xiàng)目中集成了實(shí)時(shí)編譯的開(kāi)發(fā)環(huán)境、eslint + prettier 保證代碼質(zhì)量和風(fēng)格統(tǒng)一、項(xiàng)目構(gòu)建工具、git 提交信息的規(guī)范化,這些都是一個(gè)項(xiàng)目最基本和必要的配置。

          本來(lái)到這里就結(jié)束了,但是在后續(xù)重構(gòu)腳手架的過(guò)程中發(fā)現(xiàn)一個(gè)問(wèn)題,如果每寫(xiě)一個(gè)新的 typescript 項(xiàng)目就重復(fù)一遍這個(gè)搭建流程,比如:今天需要開(kāi)發(fā)一個(gè) npm 包,明天需要開(kāi)發(fā)另外一個(gè)腳手架,這好像有點(diǎn)太麻煩了,于是就把整個(gè)搭建過(guò)程寫(xiě)成了一個(gè)腳手架,這樣在后續(xù)開(kāi)發(fā)的工作中就可以實(shí)現(xiàn)一鍵創(chuàng)建項(xiàng)目,簡(jiǎn)單、方便、舒爽

          從這篇文章中你可以學(xué)到什么 ?

          • 如何搭建一個(gè)基于 typescript 的項(xiàng)目框架
          • 在項(xiàng)目中怎么集成 eslint 和 prettier 保證代碼質(zhì)量
          • 如何規(guī)范化 git 提交信息
          • 如何為團(tuán)隊(duì)開(kāi)發(fā)自己的腳手架工具(提效)

          搭建項(xiàng)目

          初始化項(xiàng)目目錄

          mkdir ts-project && cd ts-project && npm init -y && npm i typescript -D && npx tsc --init

          這條命令的意思是在當(dāng)前目錄下創(chuàng)建一個(gè) ts-project 目錄,然后進(jìn)入 ts-project 目錄執(zhí)行 npm init -y 初始話目錄產(chǎn)生 package.json 文件,之后運(yùn)行 npm i typescript -D 在開(kāi)發(fā)環(huán)境安裝 typescript 包,之后執(zhí)行 npx tsc --init 生成 tsconfig.json 文件

          之后所有的操作都以 ts-project 為根目錄

          mkdir src && touch src/index.ts

          新建 src 目錄作為項(xiàng)目的源碼目錄(開(kāi)發(fā)目錄),并在 src 目錄下創(chuàng)建 index.ts 文件作為項(xiàng)目的入口文件

          設(shè)置 tsconfig.json

          如果一個(gè)目錄下存在 tsconfig.json 文件,那就意味著這個(gè)目錄是 typescirpt 項(xiàng)目的根目錄,tsconfig.json 文件中指定了用來(lái)編譯項(xiàng)目的根文件和編譯選項(xiàng),使用 tsc --init 生成的 tsconfig.json 文件包含了大量的選項(xiàng),其中大部分都被注釋掉了,一般我們只需要配置如下內(nèi)容即可:

          {
            "compileOnSave": true,
            "compilerOptions": {
              "target": "ES2018",
              "module": "commonjs",
              "moduleResolution": "node",
              "experimentalDecorators": true,
              "emitDecoratorMetadata": true,
              "inlineSourceMap":true,
              "noImplicitThis": true,
              "noUnusedLocals": true,
              "stripInternal": true,
              "pretty": true,
              "declaration": true,
              "outDir": "lib",
              "baseUrl": "./",
              "paths": {
                "*": ["src/*"]
              }
            },
            "exclude": [
              "lib",
              "node_modules"
            ]
          }

          @types/node

          npm i @types/node -D

          這個(gè)是 node.js 的類型定義包

          開(kāi)發(fā)環(huán)境實(shí)時(shí)編譯

          npm i ts-node-dev -D

          在 package.json 的 scripts 中增加如下內(nèi)容

          {
            "scripts": {
              "dev:comment": "啟動(dòng)開(kāi)發(fā)環(huán)境",
              "dev": "ts-node-dev --respawn --transpile-only src/index.ts"
            }
          }

          執(zhí)行 npm run dev 即可啟動(dòng)開(kāi)發(fā)環(huán)境,并且修改文件時(shí)可實(shí)時(shí)編譯

          代碼質(zhì)量

          代碼質(zhì)量對(duì)于一個(gè)系統(tǒng)的可維護(hù)性、可迭代性至關(guān)重要,特別是在多人協(xié)作一個(gè)大型項(xiàng)目中,如果沒(méi)有把控代碼質(zhì)量的工具,每人一套編碼風(fēng)格,這樣的系統(tǒng)在后期的維護(hù)難度可想而知,基本上會(huì)成為一個(gè)難以迭代升級(jí)的祖?zhèn)飨到y(tǒng),除了重寫(xiě)別無(wú)他法。

          因此控制代碼質(zhì)量的工具應(yīng)運(yùn)而生,而 ESLint 當(dāng)屬其中的佼佼者,熬死了各路的競(jìng)爭(zhēng)者;typescript 之前還在使用 TSLint,但在 2019 年 1 月 官方?jīng)Q定全面采用 ESLint 作為代碼檢查工具。

          ESLint

          采用社區(qū)的開(kāi)源配置方案 eslint-config-standard,簡(jiǎn)單直接,足以 hold 住大部分項(xiàng)目了

          npx eslint --init

          以上流程走完以后在項(xiàng)目根目錄會(huì)多出來(lái)一個(gè) .eslintrc.js 文件,接下來(lái)在 package.json 的 scripts 中增加如下配置

          {
            "scripts": {
              "eslint:comment": "使用 ESLint 檢查并自動(dòng)修復(fù) src 目錄下所有擴(kuò)展名為 .ts 的文件",
              "eslint": "eslint --fix src --ext .ts --max-warnings=0"
            }
          } 

          Prettier

          Perttier 是一個(gè)專注于統(tǒng)一代碼格式風(fēng)格的工具,可能有人會(huì)疑惑,ESLint 已經(jīng)能夠規(guī)范我們的代碼,為什么還需要 Prettier ?簡(jiǎn)單來(lái)說(shuō)是這樣的,ESLint 其實(shí)有兩種類型規(guī)則:

          • 格式規(guī)則:比如 key-spacing、comma-spacing 等
          • 質(zhì)量規(guī)則:比如 最常見(jiàn)的 no-var 等

          其中 格式規(guī)則 主要是控制代碼風(fēng)格,簡(jiǎn)單理解就是代碼看起來(lái)好看、易讀,而 質(zhì)量規(guī)則 主要是發(fā)現(xiàn)代碼中存在的潛在 bug 或者可能會(huì)制造 bug 的地方,簡(jiǎn)單來(lái)說(shuō)更多是從語(yǔ)法層面去考慮,比如現(xiàn)在禁止使用 var 聲明變量,而 prettier 則是專注于 格式規(guī)則,所以在格式方面我們選用更加專業(yè)的 Prettier。

          如果你同時(shí)使用 ESLint 和 Prettier,且在編輯器中配置了 Sava Auto Fix 時(shí),會(huì)讓你的一鍵格式化異常痛苦,因?yàn)樵?格式規(guī)則 上有沖突,所以個(gè)人建議或者說(shuō)不喜歡在編輯器中配置 ESLint 和 Prettier,三個(gè)原因:

          • 不喜歡編碼過(guò)程被編輯器因?yàn)楦袷綑z查問(wèn)題打斷
          • 通過(guò) git hook 統(tǒng)一處理格式問(wèn)題很香
          • 習(xí)慣好,一般編寫(xiě)的代碼不存在格式問(wèn)題,意思是從自身編碼習(xí)慣觸發(fā)從根本上解決問(wèn)題

          接下來(lái)就開(kāi)始安裝和配置 Prettier

          npm i prettier -D

          安裝 Prettier 所需的依賴,然后在項(xiàng)目目錄增加 .prettierrc.js,推薦配置如下:

          module.exports = {
              // 一行最多 80 字符
              printWidth: 80,
              // 使用 2 個(gè)空格縮進(jìn)
              tabWidth: 2,
              // 不使用 tab 縮進(jìn),而使用空格
              useTabs: false,
              // 行尾需要有分號(hào)
              semi: true,
              // 使用單引號(hào)代替雙引號(hào)
              singleQuote: true,
              // 對(duì)象的 key 僅在必要時(shí)用引號(hào)
              quoteProps: 'as-needed',
              // jsx 不使用單引號(hào),而使用雙引號(hào)
              jsxSingleQuote: false,
              // 末尾使用逗號(hào)
              trailingComma: 'all',
              // 大括號(hào)內(nèi)的首尾需要空格 { foo: bar }
              bracketSpacing: true,
              // jsx 標(biāo)簽的反尖括號(hào)需要換行
              jsxBracketSameLine: false,
              // 箭頭函數(shù),只有一個(gè)參數(shù)的時(shí)候,也需要括號(hào)
              arrowParens: 'always',
              // 每個(gè)文件格式化的范圍是文件的全部?jī)?nèi)容
              rangeStart: 0,
              rangeEnd: Infinity,
              // 不需要寫(xiě)文件開(kāi)頭的 @prettier
              requirePragma: false,
              // 不需要自動(dòng)在文件開(kāi)頭插入 @prettier
              insertPragma: false,
              // 使用默認(rèn)的折行標(biāo)準(zhǔn)
              proseWrap: 'preserve',
              // 根據(jù)顯示樣式?jīng)Q定 html 要不要折行
              htmlWhitespaceSensitivity: 'css',
              // 換行符使用 lf
              endOfLine: 'lf'
          }

          在 package.json 的 scripts 中補(bǔ)充如下內(nèi)容

          {
            "scripts": {
              "prettier:comment": "自動(dòng)格式化 src 目錄下的所有 .ts 文件",
              "prettier": "prettier --write \"src/**/*.ts\""
            }
          }

          如果想在編輯器中配置 ESLint 和 Prettier,具體怎么配 查看 這里,通過(guò) eslint-config-prettier 來(lái)解決沖突問(wèn)題,其作用就是關(guān)閉 ESLint 中的格式規(guī)則,只使用 Prettier 的格式規(guī)則

          commitizen

          在系統(tǒng)開(kāi)發(fā)中,如果 git 提交說(shuō)明精準(zhǔn),在后期的協(xié)作以及 bug 處理時(shí)會(huì)變的有據(jù)可查,變相的提高了系統(tǒng)的可維護(hù)性,而且可以根據(jù)規(guī)范的提交說(shuō)明快速生成開(kāi)發(fā)日志,從而方便開(kāi)發(fā)者或用戶追蹤項(xiàng)目的開(kāi)發(fā)信息和功能特性。commitizen 是一個(gè)實(shí)現(xiàn)規(guī)范提交說(shuō)明的工具。

          使用 commitizen 在項(xiàng)目中生成符合 AngularJS 規(guī)范的提交說(shuō)明,并初始化 cz-conventional-changelog 適配器

          npx commitizen init cz-conventional-changelog --save --save-exact

          初始化時(shí)主要做了三件事:

          • 在項(xiàng)目中安裝 cz-conventional-changelog 適配器依賴
          • 將適配器依賴保存到 package.json 的 devDependencies 對(duì)象中
          • 在 package.json 中新增 config.commitizen 字段,主要用于配置 cz 工具的適配器路徑

          內(nèi)容如下:

          {
            "devDependencies": {
              "cz-conventional-changelog": "^3.3.0"
            },
            "config": {
              "commitizen": {
                "path": "./node_modules/cz-conventional-changelog"
              }
            }
          }

          接下來(lái)安裝校驗(yàn)工具,負(fù)責(zé)校驗(yàn)提交信息是否符合規(guī)范

          npm i @commitlint/cli @commitlint/config-conventional -D

          在項(xiàng)目根目錄下新建 commitlint.config.js 并設(shè)置校驗(yàn)規(guī)則

          module.exports = {
            extends: ['@commitlint/config-conventional']
          };

          然后在 package.json 的 scripts 中增加如下內(nèi)容

          {
            "scripts": {
              "commit:comment": "引導(dǎo)設(shè)置規(guī)范化的提交信息",
              "commit": "cz"
            }
          }

          接下來(lái),就只能使用規(guī)范化的提交信息了,如果不知道規(guī)范是什么,可在 git add . 之后執(zhí)行 npm run commit 代替 git commit,會(huì)彈出一個(gè)列表,引導(dǎo)你一步步的填充符合規(guī)范的提交信息,熟練以后亦可用 git commit

          husky@4 和 lint-staged

          注意 以下內(nèi)容為第 4 版 husky 的使用方式。到目前為止,如果你的項(xiàng)目還沒(méi)有執(zhí)行過(guò) git init,即項(xiàng)目沒(méi)有被 git 管理,則一定要先執(zhí)行 git init 然后再往后進(jìn)行,否則后面你需要重新安裝一遍 husky

          npm i husky@4 lint-staged -D

          在 package.json 中添加如下內(nèi)容

          {
            "husky": {
              "hooks": {
                "pre-commit": "lint-staged",
                "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
              }
            },
            "lint-staged": {
              "*.ts": ["npm run eslint", "npm run prettier"]
            }
          }

          之前設(shè)置的 ESLint、Prettier 以及 commitizen 到目前為止都只限于開(kāi)發(fā)者手動(dòng)執(zhí)行 npm run xx 才能生效,這可不行,因?yàn)檫@一點(diǎn)都不智能,而且萬(wàn)一開(kāi)發(fā)者忘執(zhí)行命令了怎么辦 ?

          這時(shí)候就需要 husky 和 lint-staged 出場(chǎng)了,上述配置的原理其實(shí)就是監(jiān)聽(tīng)了 git hook 腳本的執(zhí)行,在特定的命令執(zhí)行前(pre-commit) 執(zhí)行相應(yīng)的命令(lint-staged)。

          husky v5

          注意 這部分為 husky@5 的使用方式,和第 4 版不一樣,如果還按照第 4 版的方式使用 husky@5 有問(wèn)題,網(wǎng)上的解決方案也不可行

          • 安裝 husky npm i husky -D 復(fù)制代碼
          • 使用 git hooks npx husky install 復(fù)制代碼 這一步執(zhí)行完以后會(huì)在項(xiàng)目根目錄生成一個(gè) .husky 的文件夾
          • 添加 hooks npx husky add .husky/pre-commit "npm test" 復(fù)制代碼 會(huì)在 .husky 目錄下生成一個(gè) pre-commit 腳本文件

          接下來(lái)使用 git commit -m "message" 就會(huì)看到 hook 生效了。

          構(gòu)建

          因?yàn)檫@個(gè)項(xiàng)目主要用于開(kāi)發(fā)一些簡(jiǎn)單的 typescript 項(xiàng)目,比如項(xiàng)目組需要封裝自己的 npm 包,所以就沒(méi)有集成第三方的構(gòu)建工具,直接用 typescript 自己的構(gòu)建能力即可,簡(jiǎn)單易用,沒(méi)有學(xué)習(xí)成本

          在 package.json 中添加如下內(nèi)容

          {
            "scripts": {
              "build:comment": "構(gòu)建",
              "build": "npm run eslint && npm run prettier && rm -rf lib && tsc --build",
            }
          }

          小結(jié)

          好了,到這里項(xiàng)目就搭建好了,雖然還有一些可優(yōu)化和擴(kuò)展的地方,但是用于開(kāi)發(fā)一個(gè)簡(jiǎn)單的 npm 包或者腳手架的項(xiàng)目來(lái)說(shuō)已經(jīng)足夠了,如有需要可以基于以上內(nèi)容再進(jìn)行進(jìn)一步的補(bǔ)充和擴(kuò)展,希望大家能從里面得到自己需要的內(nèi)容。

          腳手架

          接下來(lái)就將上面的整個(gè)搭建過(guò)程封裝成一個(gè)腳手架,腳手架的開(kāi)發(fā)就在上面搭建的項(xiàng)目中進(jìn)行

          準(zhǔn)備工作

          常用工具包

          開(kāi)發(fā)一個(gè)腳手架,一般都需要一些工具包的支持,項(xiàng)目中使用到了以下工具包:

          • commander 完整的 node.js 命令行解決方案,靈感來(lái)自 Ruby 的 commander
          • chalk 粉飾你的終端,給你的終端文字添加樣式
          • shelljs 讓你在 node.js 中使用 unix shell 命令
          • inquirer 通用交互式命令行用戶界面,收集用戶的選擇
          • clear-console 清空命令行的當(dāng)前界面,類似于瀏覽器控制臺(tái)的 clear() 和 命令行下的 clear
          • ora 進(jìn)一步豐富你的命令行,支持添加一些圖標(biāo)、動(dòng)效
          • download-git-repo 讓你可以使用 node.js 從 git 倉(cāng)庫(kù)下載代碼

          上面列出的這些基本上就是目前開(kāi)發(fā)一款腳手架常用的工具,接下來(lái)安裝項(xiàng)目需要用到的工具包:

          npm i commander chalk shelljs inquirer clear-console -S

          本地調(diào)試

          在項(xiàng)目根目錄下的 pacakge.json 中增加如下內(nèi)容:

          {
            "bin": {
              "ts-cli": "./bin/ts-cli.js"
            }
          }

          bin 表示命令(ts-cli)的可執(zhí)行文件的位置,接下來(lái)在項(xiàng)目根目錄執(zhí)行 npm link,將 package.json 中的屬性 bin 的值路徑添加全局鏈接,在命令行中執(zhí)行 ts-cli 就會(huì)執(zhí)行 ./bin/ts-cli.js 文件

          當(dāng)用戶安裝帶有 bin 字段的包時(shí),如果是全局安裝,npm 將會(huì)使用符號(hào)鏈接把這些文件鏈接到/usr/local/node_modules/.bin/(即全局的 node_modules/.bin 中);如果是本地安裝,會(huì)鏈接到./node_modules/.bin/。

          開(kāi)發(fā)結(jié)束可執(zhí)行 npm unlink ts-cli 去掉 ts-cli 的鏈接,如果不幸你執(zhí)行 npm link 命令之后你改變了你的目錄名,在 unlink 時(shí)會(huì)無(wú)效,只能手動(dòng)去全局的 node_modules 中刪除對(duì)應(yīng)的軟連接

          在項(xiàng)目根目錄下添加 bin 目錄,然后在 bin 目錄下新建 ts-cli.js,文件內(nèi)容如下:

          #!/usr/bin/env node
          
          // 將構(gòu)建目錄(lib)下的 index.js 作為腳手架的入口
          require('../lib/index')

          接下來(lái)正式進(jìn)入開(kāi)發(fā)階段

          開(kāi)發(fā)

          這是源碼的一個(gè)目錄結(jié)構(gòu)

          /src/index.ts

          import { program } from 'commander';
          import create from './order/create';
          
          // ts-cli -v、ts-cli --version
          // 臨時(shí)禁用規(guī)則,保證這里可以通過(guò) require 方法獲取 package.json 中的版本號(hào)
          /* eslint-disable @typescript-eslint/no-var-requires */
          program
            .version(`${require('../package.json').version}`, '-v --version')
            .usage('<command> [options]');
          
          // ts-cli create newPro
          program
            .command('create <app-name>')
            .description('Create new project from => ts-cli create yourProjectName')
            .action(async (name: string) => {
              // 創(chuàng)建命令具體做的事情都在這里,name 是你指定的 newPro
              await create(name);
            });
          
          program.parse(process.argv);
          

          /src/order/create.ts

          /**
           * create 命令的具體任務(wù)
           */
          
          import {
            changePackageInfo,
            end,
            initProjectDir,
            installDevEnviroment,
            installFeature,
            installTSAndInit,
            installTypesNode,
            isFileExist,
            selectFeature,
          } from '../utils/create';
          
          // create 命令
          export default async function create(projecrName: string): Promise<void> {
            // 判斷文件是否已經(jīng)存在
            isFileExist(projecrName);
            // 選擇需要的功能
            const feature = await selectFeature();
            // 初始化項(xiàng)目目錄
            initProjectDir(projecrName);
            // 改寫(xiě)項(xiàng)目的 package.json 基本信息,比如 name、description
            changePackageInfo(projecrName);
            // 安裝 typescript 并初始化
            installTSAndInit();
            // 安裝 @types/node
            installTypesNode();
            // 安裝開(kāi)發(fā)環(huán)境,支持實(shí)時(shí)編譯
            installDevEnviroment();
            // 安裝 feature
            installFeature(feature);
            // 結(jié)束
            end(projecrName);
          }
          

          /src/utils/create.ts

          /**
           * create 命令需要用到的所有方法
           */
          import {
            getProjectPath,
            PackageJSON,
            JSON,
            printMsg,
            readJsonFile,
            writeJsonFile,
            clearConsole,
          } from '../utils/common';
          import { existsSync } from 'fs';
          import { prompt } from 'inquirer';
          import { blue, cyan, gray, red, yellow } from 'chalk';
          import * as shell from 'shelljs';
          import * as installFeatureMethod from './installFeature';
          
          /**
           * 驗(yàn)證當(dāng)前目錄下是否已經(jīng)存在指定文件,如果存在則退出進(jìn)行
           * @param filename 文件名
           */
          export function isFileExist(filename: string): void {
            // 文件路徑
            const file = getProjectPath(filename);
            // 驗(yàn)證文件是否已經(jīng)存在,存在則推出進(jìn)程
            if (existsSync(file)) {
              printMsg(red(`${file} 已經(jīng)存在`));
              process.exit(1);
            }
          }
          
          /**
           * 交互式命令行,讓用戶自己選擇需要的功能
           * return ['ESLint', 'Prettier', 'CZ']
           */
          export async function selectFeature(): Promise<Array<string>> {
            // 清空命令行
            clearConsole();
            // 輸出信息
            /* eslint-disable @typescript-eslint/no-var-requires */
            printMsg(blue(`TS CLI v${require('../../package.json').version}`));
            printMsg('Start initializing the project:');
            printMsg('');
            // 選擇功能,這里配合 下面的 installFeature 方法 和 ./installFeature.ts 文件為腳手架提供了良好的擴(kuò)展機(jī)制
            // 將來(lái)擴(kuò)展其它功能只需要在 choices 數(shù)組中增加配置項(xiàng),然后在 ./installFeature.ts 文件中增加相應(yīng)的安裝方法即可
            const { feature } = await prompt([
              {
                name: 'feature',
                type: 'checkbox',
                message: 'Check the features needed for your project',
                choices: [
                  { name: 'ESLint', value: 'ESLint' },
                  { name: 'Prettier', value: 'Prettier' },
                  { name: 'CZ', value: 'CZ' },
                ],
              },
            ]);
          
            return feature as Array<string>;
          }
          
          /**
           * 初始化項(xiàng)目目錄
           */
          export function initProjectDir(projectName: string): void {
            shell.exec(`mkdir ${projectName}`);
            shell.cd(projectName);
            shell.exec('npm init -y');
          }
          
          /**
           * 改寫(xiě)項(xiàng)目中 package.json 的 name、description
           */
          export function changePackageInfo(projectName: string): void {
            const packageJSON: PackageJSON = readJsonFile<PackageJSON>('./package.json');
            packageJSON.name = packageJSON.description = projectName;
            writeJsonFile<PackageJSON>('./package.json', packageJSON);
          }
          
          /**
           * 安裝 typescript 并初始化
           */
          export function installTSAndInit(): void {
            // 安裝 typescript 并執(zhí)行命令 tsc --init 生成 tsconfig.json
            shell.exec('npm i typescript -D && npx tsc --init');
            // 覆寫(xiě) tsconfig.json
            const tsconfigJson: JSON = {
              compileOnSave: true,
              compilerOptions: {
                target: 'ES2018',
                module: 'commonjs',
                moduleResolution: 'node',
                experimentalDecorators: true,
                emitDecoratorMetadata: true,
                inlineSourceMap: true,
                noImplicitThis: true,
                noUnusedLocals: true,
                stripInternal: true,
                pretty: true,
                declaration: true,
                outDir: 'lib',
                baseUrl: './',
                paths: {
                  '*': ['src/*'],
                },
              },
              exclude: ['lib', 'node_modules'],
            };
            writeJsonFile<JSON>('./tsconfig.json', tsconfigJson);
            // 創(chuàng)建 src 目錄和 /src/index.ts
            shell.exec('mkdir src && touch src/index.ts');
          }
          
          /**
           * 安裝 @types/node
           * 這是 node.js 的類型定義包
           */
          export function installTypesNode(): void {
            shell.exec('npm i @types/node -D');
          }
          
          /**
           * 安裝開(kāi)發(fā)環(huán)境,支持實(shí)時(shí)編譯
           */
          export function installDevEnviroment(): void {
            shell.exec('npm i ts-node-dev -D');
            /**
             * 在 package.json 的 scripts 中增加如下內(nèi)容
             * "dev:comment": "啟動(dòng)開(kāi)發(fā)環(huán)境",
             * "dev": "ts-node-dev --respawn --transpile-only src/index.ts"
             */
            const packageJson = readJsonFile<PackageJSON>('./package.json');
            packageJson.scripts['dev:comment'] = '啟動(dòng)開(kāi)發(fā)環(huán)境';
            packageJson.scripts['dev'] =
              'ts-node-dev --respawn --transpile-only src/index.ts';
            writeJsonFile<PackageJSON>('./package.json', packageJson);
          }
          
          /**
           * 安裝用戶選擇的功能
           * @param feature 功能列表
           */
          export function installFeature(feature: Array<string>): void {
            feature.forEach((item) => {
              const func = (installFeatureMethod[
                `install${item}`
              ] as unknown) as () => void;
              func();
            });
            // 安裝 husky 和 lint-staged
            installHusky(feature);
            // 安裝構(gòu)建工具
            installFeatureMethod.installBuild(feature);
          }
          
          /**
           * 安裝 husky 和 lint-staged,并根據(jù)功能設(shè)置相關(guān)命令
           * @param feature 用戶選擇的功能列表
           */
          function installHusky(feature: Array<string>): void {
            // feature 副本
            const featureBak = JSON.parse(JSON.stringify(feature));
          
            // 設(shè)置 hook
            const hooks = {};
            // 判斷用戶是否選擇了 CZ,有則設(shè)置 hooks
            if (featureBak.includes('CZ')) {
              hooks['commit-msg'] = 'commitlint -E HUSKY_GIT_PARAMS';
            }
          
            // 設(shè)置 lintStaged
            const lintStaged: Array<string> = [];
            if (featureBak.includes('ESLint')) {
              lintStaged.push('eslint');
            }
            if (featureBak.includes('Prettier')) {
              lintStaged.push('prettier');
            }
          
            installFeatureMethod.installHusky(hooks, lintStaged);
          }
          
          /**
           * 整個(gè)項(xiàng)目安裝結(jié)束,給用戶提示信息
           */
          export function end(projectName: string): void {
            printMsg(`Successfully created project ${yellow(projectName)}`);
            printMsg('Get started with the following commands:');
            printMsg('');
            printMsg(`${gray('$')} ${cyan('cd ' + projectName)}`);
            printMsg(`${gray('$')} ${cyan('npm run dev')}`);
            printMsg('');
          }

          /src/utils/installFeature.ts

          /**
           * 實(shí)現(xiàn)各個(gè)功能的安裝方法
           */
          import * as shell from 'shelljs';
          import { writeFileSync } from 'fs';
          import { PackageJSON, printMsg, readJsonFile, writeJsonFile } from './common';
          import { red } from 'chalk';
          
          /**
           * 安裝 ESLint
           */
          export function installESLint(): void {
            shell.exec(
              'npm i eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin -D',
            );
            // 添加 .eslintrc.js
            const eslintrc = `module.exports = {
            "env": {
              "es2021": true,
              "node": true
            },
            "extends": [
              "eslint:recommended",
              "plugin:@typescript-eslint/recommended"
            ],
            "parser": "@typescript-eslint/parser",
            "parserOptions": {
              "ecmaVersion": 12,
              "sourceType": "module"
            },
            "plugins": [
              "@typescript-eslint"
            ],
            "rules": {
            }
          };
            `;
            try {
              writeFileSync('./.eslintrc.js', eslintrc, { encoding: 'utf-8' });
            } catch (err) {
              printMsg(`${red('Failed to write .eslintrc.js file content')}`);
              printMsg(`${red('Please add the following content in .eslintrc.js')}`);
              printMsg(`${red(eslintrc)}`);
            }
          
            // 改寫(xiě) package.json
            const packageJson = readJsonFile<PackageJSON>('./package.json');
            packageJson.scripts['eslint:comment'] =
              '使用 ESLint 檢查并自動(dòng)修復(fù) src 目錄下所有擴(kuò)展名為 .ts 的文件';
            packageJson.scripts['eslint'] = 'eslint --fix src --ext .ts --max-warnings=0';
            writeJsonFile<PackageJSON>('./package.json', packageJson);
          }
          
          /**
           * 安裝 Prettier
           */
          export function installPrettier(): void {
            shell.exec('npm i prettier -D');
            // 添加 .prettierrc.js
            const prettierrc = `module.exports = {
            // 一行最多 80 字符
            printWidth: 80,
            // 使用 2 個(gè)空格縮進(jìn)
            tabWidth: 2,
            // 不使用 tab 縮進(jìn),而使用空格
            useTabs: false,
            // 行尾需要有分號(hào)
            semi: true,
            // 使用單引號(hào)代替雙引號(hào)
            singleQuote: true,
            // 對(duì)象的 key 僅在必要時(shí)用引號(hào)
            quoteProps: 'as-needed',
            // jsx 不使用單引號(hào),而使用雙引號(hào)
            jsxSingleQuote: false,
            // 末尾使用逗號(hào)
            trailingComma: 'all',
            // 大括號(hào)內(nèi)的首尾需要空格 { foo: bar }
            bracketSpacing: true,
            // jsx 標(biāo)簽的反尖括號(hào)需要換行
            jsxBracketSameLine: false,
            // 箭頭函數(shù),只有一個(gè)參數(shù)的時(shí)候,也需要括號(hào)
            arrowParens: 'always',
            // 每個(gè)文件格式化的范圍是文件的全部?jī)?nèi)容
            rangeStart: 0,
            rangeEnd: Infinity,
            // 不需要寫(xiě)文件開(kāi)頭的 @prettier
            requirePragma: false,
            // 不需要自動(dòng)在文件開(kāi)頭插入 @prettier
            insertPragma: false,
            // 使用默認(rèn)的折行標(biāo)準(zhǔn)
            proseWrap: 'preserve',
            // 根據(jù)顯示樣式?jīng)Q定 html 要不要折行
            htmlWhitespaceSensitivity: 'css',
            // 換行符使用 lf
            endOfLine: 'lf'
          };
            `;
            try {
              writeFileSync('./.prettierrc.js', prettierrc, { encoding: 'utf-8' });
            } catch (err) {
              printMsg(`${red('Failed to write .prettierrc.js file content')}`);
              printMsg(`${red('Please add the following content in .prettierrc.js')}`);
              printMsg(`${red(prettierrc)}`);
            }
            // 改寫(xiě) package.json
            const packageJson = readJsonFile<PackageJSON>('./package.json');
            packageJson.scripts['prettier:comment'] =
              '自動(dòng)格式化 src 目錄下的所有 .ts 文件';
            packageJson.scripts['prettier'] = 'prettier --write "src/**/*.ts"';
            writeJsonFile<PackageJSON>('./package.json', packageJson);
          }
          
          /**
           * 安裝 CZ,規(guī)范 git 提交信息
           */
          export function installCZ(): void {
            shell.exec(
              'npx commitizen init cz-conventional-changelog --save --save-exact',
            );
            shell.exec('npm i @commitlint/cli @commitlint/config-conventional -D');
            // 添加 commitlint.config.js
            const commitlint = `module.exports = {
            extends: ['@commitlint/config-conventional']
          };
            `;
            try {
              writeFileSync('./commitlint.config.js', commitlint, { encoding: 'utf-8' });
            } catch (err) {
              printMsg(`${red('Failed to write commitlint.config.js file content')}`);
              printMsg(
                `${red('Please add the following content in commitlint.config.js')}`,
              );
              printMsg(`${red(commitlint)}`);
            }
            // 改寫(xiě) package.json
            const packageJson = readJsonFile<PackageJSON>('./package.json');
            packageJson.scripts['commit:comment'] = '引導(dǎo)設(shè)置規(guī)范化的提交信息';
            packageJson.scripts['commit'] = 'cz';
            writeJsonFile<PackageJSON>('./package.json', packageJson);
          }
          
          /**
           * 安裝 husky 和 lint-staged,以實(shí)現(xiàn) git commit 時(shí)自動(dòng)化校驗(yàn)
           * @param hooks,需要自動(dòng)執(zhí)行的鉤子
           * @param lintStaged,需要鉤子運(yùn)行的命令
           */
          export function installHusky(
            hooks: { [key: string]: string },
            lintStaged: Array<string>,
          ): void {
            // 初始化 git 倉(cāng)庫(kù)
            shell.exec('git init');
            // 在安裝 husky 和 lint-staged
            shell.exec('npm i husky lint-staged -D');
            // 設(shè)置 package.json
            const packageJson = readJsonFile<PackageJSON>('./package.json');
            packageJson['husky'] = {
              hooks: {
                'pre-commit': 'lint-staged',
                ...hooks,
              },
            };
            packageJson['lint-staged'] = {
              '*.ts': lintStaged.map((item) => `npm run ${item}`),
            };
            writeJsonFile<PackageJSON>('./package.json', packageJson);
          }
          
          /**
           * 安裝構(gòu)建工具,目前主要用于小項(xiàng)目,所以使用 typescript 原生的構(gòu)建功能即可
           */
          export function installBuild(feature: Array<string>): void {
            // 設(shè)置 package.json
            const packageJson = readJsonFile<PackageJSON>('./package.json');
            packageJson.scripts['build:comment'] = '構(gòu)建';
            let order = '';
            if (feature.includes('ESLint')) {
              order += 'npm run eslint';
            }
            if (feature.includes('Prettier')) {
              order += ' && npm run prettier';
            }
            order += ' && rm -rf lib && tsc --build';
            packageJson.scripts['build'] = order;
            writeJsonFile<PackageJSON>('./package.json', packageJson);
          }
          

          /src/utils/common.ts

          /**
           * 放一些通用的工具方法
           */
          import { readFileSync, writeFileSync } from 'fs';
          import { resolve } from 'path';
          import * as clear from 'clear-console';
          
          export interface PackageJSON {
            name: string;
            version: string;
            description: string;
            scripts: {
              [key: string]: string;
            };
          }
          
          export interface JSON {
            [key: string]: unknown;
          }
          
          /**
           * 讀取指定路徑下 json 文件
           * @param filename json 文件的路徑
           */
          export function readJsonFile<T>(filename: string): T {
            return JSON.parse(readFileSync(filename, { encoding: 'utf-8', flag: 'r' }));
          }
          
          /**
           * 覆寫(xiě)指定路徑下的 json 文件
           * @param filename json 文件的路徑
           * @param content  json 內(nèi)容
           */
          export function writeJsonFile<T>(filename: string, content: T): void {
            writeFileSync(filename, JSON.stringify(content, null, 2));
          }
          
          /**
           * 獲取項(xiàng)目絕對(duì)路徑
           * @param projectName 項(xiàng)目名
           */
          export function getProjectPath(projectName: string): string {
            return resolve(process.cwd(), projectName);
          }
          
          /**
           * 打印信息
           * @param msg 信息
           */
          export function printMsg(msg: string): void {
            console.log(msg);
          }
          
          /**
           * 清空命令行
           */
          export function clearConsole(): void {
            clear();
          }
          

          構(gòu)建

          執(zhí)行 npm run build 進(jìn)行構(gòu)建,構(gòu)建時(shí)會(huì)進(jìn)行代碼質(zhì)量和風(fēng)格的檢查,有些問(wèn)題可以自動(dòng)修復(fù),有些不行,不行的按照提示手動(dòng)修復(fù)一下即可,然后重新構(gòu)建

          構(gòu)建完成以后找個(gè)測(cè)試目錄,執(zhí)行 ts-cli -v 或者 ts-cli --version 查看腳手架的版本

          執(zhí)行 ts-cli create test 可創(chuàng)建一個(gè)名為 test 的 typescript 項(xiàng)目

          發(fā)布到 npm

          準(zhǔn)備

          修改 package.json 中的如下內(nèi)容

          {
            "name": "@liyongning/ts-cli"
            "main": "./lib/index.js",
            "keywords": ["typescript", "cli", "typescript 腳手架", "ts 腳手架", "ts-cli", "腳手架"],
            "author": "李永寧",
            "files": ["package.json", "README.md", "lib"],
            "repository": {
              "type": "git",
              "url": "https://github.com/liyongning/ts-cli.git"
            },
          }
          • name: 包名,在包名稱前加自己的 npm 賬戶名,采用 npm scope 的方式,包目錄的組織方式和普通包不一樣,而且可以有效的避免和他人的包名沖突
          • main:表示包的入口位置
          • keywords:關(guān)鍵字,方面別人搜索到你的包
          • files:告訴 npm,publish 時(shí)發(fā)布哪些包到 npm 倉(cāng)庫(kù)
          • repository:項(xiàng)目倉(cāng)庫(kù)

          npm 的賬戶名、密碼就不用說(shuō),必不可少

          發(fā)布

          在項(xiàng)目根目錄下增加一個(gè)發(fā)布腳本 publish.sh

          #!/bin/bash
          
          echo '開(kāi)始構(gòu)建腳手架'
          
          npm run build
          
          echo '腳手架構(gòu)建完成,現(xiàn)在發(fā)布'
          
          npm publish --access public

          接下來(lái)開(kāi)始發(fā)布,在項(xiàng)目根目錄下一次運(yùn)行如下命令:

          npm login

          根據(jù)提示依次輸入相關(guān)信息,然后執(zhí)行下面的命令完成發(fā)布

          sh publish.sh

          登陸 npm 查看

          結(jié)束

          好了,到這里項(xiàng)目就搭建和腳手架的封裝就徹底結(jié)束了,雖然還有一些可優(yōu)化和擴(kuò)展的地方,但是對(duì)于開(kāi)發(fā)一個(gè)簡(jiǎn)單的庫(kù)或者腳手架的項(xiàng)目來(lái)說(shuō)這個(gè)腳手架已經(jīng)完全夠用了

          如有需要也可以自行去擴(kuò)展,因?yàn)槟_手架內(nèi)置了不錯(cuò)的擴(kuò)展性,不論是為已有的 create 命令增加新的功能,還是新增一個(gè)命令,都很簡(jiǎn)單,方便你根據(jù)自身需要進(jìn)行二次開(kāi)發(fā)


          上一篇:php基礎(chǔ)知識(shí)

          主要參考:https://www.w3school.com.cn

          思維導(dǎo)圖

          思維導(dǎo)圖第一版

          1、web編程基礎(chǔ)-什么是HTML、CSS、Javascript

          web網(wǎng)站可以說(shuō)是互聯(lián)網(wǎng)的基礎(chǔ)。每個(gè)網(wǎng)站,可以比喻為一座座房子。寬帶網(wǎng)絡(luò),就是房子門(mén)前的路。url地址,就是房子的門(mén)牌標(biāo)志。HTML代碼,就是建造房子的建筑材料(磚頭、水泥、鋼筋);CSS代碼,就是裝修房子的裝修材料;那么Javascript代碼就是這房子的水電了? JS代碼則更像是未來(lái)世界可以讓房子成為變形金剛的智能機(jī)器。因此,一些展示“老房子”的瀏覽器,可能并不支持Javascript。

          定義:

          HTML(Hyper Text Markup Language),是使用標(biāo)記標(biāo)簽來(lái)描述網(wǎng)頁(yè)的一種超文本標(biāo)記語(yǔ)言。

          Web 瀏覽器的作用是讀取 HTML 文檔,并以網(wǎng)頁(yè)的形式顯示出它們。瀏覽器不會(huì)顯示 HTML 標(biāo)簽,而是使用標(biāo)簽來(lái)解釋頁(yè)面的內(nèi)容。HTML定義網(wǎng)頁(yè)的內(nèi)容。

          CSS(Cascading Style Sheets),指層疊樣式表。樣式定義如何顯示HTML元素,規(guī)定網(wǎng)頁(yè)的布局。

          Javascript 則是屬于HTML和Web的編程語(yǔ)言,對(duì)網(wǎng)頁(yè)進(jìn)行編程。

          Jquery 是一個(gè)Javascript函數(shù)庫(kù)

          2、環(huán)境配置

          參考上一篇:php基礎(chǔ)知識(shí),安裝-集成環(huán)境與編輯器

          推薦使用 phpstudy + phpstorm

          操作步驟:1、在phpstudy 安裝目錄下,把代碼文件放大到根目錄www/ 下。

          2、瀏覽器直接訪問(wèn) localhost/index.html即可看到效果。

          3、HTML

          HTML元素:是從開(kāi)始標(biāo)簽(start tag)到結(jié)束標(biāo)簽(end tag)的所有代碼。

          例如:<p>前面這個(gè)是開(kāi)始標(biāo)簽,中間文字是元素內(nèi)容,后面這個(gè)是結(jié)束標(biāo)簽</p>

          HTML 標(biāo)簽可以擁有屬性。屬性提供了有關(guān) HTML 元素的更多的信息。

          屬性總是以名稱/值對(duì)的形式出現(xiàn),比如:name="value"。

          屬性總是在 HTML 元素的開(kāi)始標(biāo)簽中規(guī)定。

          常用HTML元素屬性:

          class :規(guī)定元素的類名(classname),一個(gè)html文件里面多個(gè)標(biāo)簽可以擁有相同的類名。

          id :規(guī)定元素的唯一 id,一個(gè)html文件里面id不能相同。

          style :規(guī)定元素的行內(nèi)樣式(inline style)

          3.1、常用HTML標(biāo)簽:

          1、標(biāo)題:標(biāo)題(Heading)是通過(guò) <h1> - <h6> 等標(biāo)簽進(jìn)行定義的。<h1> 定義最大的標(biāo)題。<h6> 定義最小的標(biāo)題。

          2、段落:通過(guò) <p> 標(biāo)簽定義。

          3、注釋標(biāo)簽 <!-- 與 --> 用于在 HTML 插入注釋。

          4、鏈接:<a href="http://www.yummuu.com/">www.yummuu.com</a> 。href 屬性規(guī)定鏈接的目標(biāo)。開(kāi)始標(biāo)簽和結(jié)束標(biāo)簽之間的文字被作為超級(jí)鏈接來(lái)顯示。

          5、圖像:<img src="yummuu.png" alt="Yummuu" /> 。src 圖像源屬性,alt替換文本屬性。

          6、表格標(biāo)簽:

          7、列表標(biāo)簽

          8、塊級(jí)元素和內(nèi)聯(lián)元素

          <div> 元素是塊級(jí)元素,它是可用于組合其他 HTML 元素的容器。

          <div> 元素沒(méi)有特定的含義。除此之外,由于它屬于塊級(jí)元素,瀏覽器會(huì)在其前后顯示折行。如果與 CSS 一同使用,<div> 元素可用于對(duì)大的內(nèi)容塊設(shè)置樣式屬性。

          <div> 元素的另一個(gè)常見(jiàn)的用途是文檔布局。它取代了使用表格定義布局的老式方法。使用 <table> 元素進(jìn)行文檔布局不是表格的正確用法。<table> 元素的作用是顯示表格化的數(shù)據(jù)。

          <span> 元素是內(nèi)聯(lián)元素,可用作文本的容器。

          <span> 元素也沒(méi)有特定的含義。

          當(dāng)與 CSS 一同使用時(shí),<span> 元素可用于為部分文本設(shè)置樣式屬性。

          兩者的區(qū)別:就是在顯示時(shí)是否起新行。塊級(jí)元素會(huì)起新行,而內(nèi)聯(lián)元素則不會(huì)。

          9、框架與內(nèi)聯(lián)框架:frame,<iframe src=" " name=" "></iframe>

          10、腳本:<script> 定義客戶端腳本,如Javascript;<noscript> 為不支持客戶端腳本的瀏覽器定義替代內(nèi)容。

          11、頭部元素:

          <head> 元素是所有頭部元素的容器。<head> 內(nèi)的元素可包含腳本,指示瀏覽器在何處可以找到樣式表,提供元信息,等等。

          以下標(biāo)簽都可以添加到 head 部分:<title>、<base>、<link>、<meta>、<script> 以及 <style>。

          <title>:在所有 HTML/XHTML 文檔中都是必需的。它能夠定義瀏覽器工具欄中的標(biāo)題,提供頁(yè)面被添加到收藏夾時(shí)顯示的標(biāo)題,顯示在搜索引擎結(jié)果中的頁(yè)面標(biāo)題。

          <base>:為頁(yè)面上的所有鏈接規(guī)定默認(rèn)地址或默認(rèn)目標(biāo)(target)

          <link> :定義文檔與外部資源之間的關(guān)系。最常用于連接樣式表。

          <style>:用于為 HTML 文檔定義樣式信息。

          <meta> 標(biāo)簽提供關(guān)于 HTML 文檔的元數(shù)據(jù)。元數(shù)據(jù)不會(huì)顯示在頁(yè)面上,但是對(duì)于機(jī)器是可讀的。典型的情況是,meta 元素被用于規(guī)定頁(yè)面的描述、關(guān)鍵詞、文檔的作者、最后修改時(shí)間以及其他元數(shù)據(jù)。

          <meta> 標(biāo)簽始終位于 head 元素中。元數(shù)據(jù)可用于瀏覽器(如何顯示內(nèi)容或重新加載頁(yè)面),搜索引擎(關(guān)鍵詞),或其他 web 服務(wù)。

          <script> 標(biāo)簽用于定義客戶端腳本,比如 JavaScript。

          12、HTML實(shí)體

          在 HTML 中不能使用小于號(hào)(<)和大于號(hào)(>),這是因?yàn)闉g覽器會(huì)誤認(rèn)為它們是標(biāo)簽。如果希望正確地顯示預(yù)留字符,我們必須在 HTML 源代碼中使用字符實(shí)體(character entities)。


          13、表單元素:

          <form> :定義 HTML 表單。

          <input> :是最重要的表單元素。<input> 元素有很多形態(tài),根據(jù)不同的 type 屬性。

          input的輸入類型type有text、password、submit、radio、checkbox、button;(HTML5新增)number、date、color、range、month、week、time、datetime、datetime-local、email、search、tel、url。

          input的常用屬性:value、readonly、disabled、size、maxlength;(HTML5新增)required、multiple、pattern、min和max、list、height和width、autocomplete

          <select> :定義下拉列表 <option> 元素定義待選擇的選項(xiàng)。列表通常會(huì)把首個(gè)選項(xiàng)顯示為被選選項(xiàng)。您能夠通過(guò)添加 selected 屬性來(lái)定義預(yù)定義選項(xiàng)。

          <textarea>:定義多行輸入字段(文本域)

          <button>:定義可點(diǎn)擊的按鈕

          4、CSS

          樣式表允許以多種方式規(guī)定樣式信息。樣式可以規(guī)定在單個(gè)的 HTML 元素中,在 HTML 頁(yè)的頭元素中,或在一個(gè)外部的 CSS 文件中。甚至可以在同一個(gè) HTML 文檔內(nèi)部引用多個(gè)外部樣式表。

          層疊次序

          當(dāng)同一個(gè) HTML 元素被不止一個(gè)樣式定義時(shí),會(huì)使用哪個(gè)樣式呢?

          一般而言,所有的樣式會(huì)根據(jù)下面的規(guī)則層疊于一個(gè)新的虛擬樣式表中,其中數(shù)字 4 擁有最高的優(yōu)先權(quán)。

          1、瀏覽器缺省設(shè)置

          2、外部樣式表

          3、內(nèi)部樣式表(位于 <head> 標(biāo)簽內(nèi)部)

          4、內(nèi)聯(lián)樣式(在 HTML 元素內(nèi)部)

          因此,內(nèi)聯(lián)樣式(在 HTML 元素內(nèi)部)擁有最高的優(yōu)先權(quán),這意味著它將優(yōu)先于以下的樣式聲明:<head> 標(biāo)簽中的樣式聲明,外部樣式表中的樣式聲明,或者瀏覽器中的樣式聲明(缺省值)。

          4.1、CSS語(yǔ)法

          CSS 規(guī)則由兩個(gè)主要的部分構(gòu)成:選擇器,以及一條或多條聲明。

          selector {declaration1; declaration2; ... declarationN }

          每條聲明由一個(gè)屬性和一個(gè)值組成。

          屬性(property)是您希望設(shè)置的樣式屬性(style attribute)。每個(gè)屬性有一個(gè)值。屬性和值被冒號(hào)分開(kāi)。

          selector {property: value}

          4.2、CSS選擇器

          1、派生選擇器:

          通過(guò)依據(jù)元素在其位置的上下文關(guān)系來(lái)定義樣式,例如: h1 span{color:red;}

          2、id選擇器:

          id 選擇器可以為標(biāo)有特定 id 的 HTML 元素指定特定的樣式。id 選擇器以 "#" 來(lái)定義。

          3、類選擇器:

          以一個(gè)點(diǎn)號(hào)顯示,例如: .className{text-align: center;}

          4、屬性選擇器:

          對(duì)帶有指定屬性的 HTML 元素設(shè)置樣式。例如: div[rel=’mm’]{ color:’#000’;}

          可以為擁有指定屬性的 HTML 元素設(shè)置樣式,而不僅限于 class 和 id 屬性。

          注釋:只有在規(guī)定了 !DOCTYPE 時(shí),IE7 和 IE8 才支持屬性選擇器。在 IE6 及更低的版本中,不支持屬性選擇。

          5、后代選擇器(包含選擇器):可以選擇作為某元素后代的元素

          6、子元素選擇器:選擇作為某元素子元素的元素。例如:h1>span{font-size:16px;}

          7、相鄰兄弟選擇器:可選擇緊接在另一元素后的元素,且二者有相同父元素。

          例如:h1 + p {margin-top:50px;}

          8、偽類:用于向某些選擇器添加特殊的效果。

          :active 向被激活的元素添加樣式

          :focus 向擁有鍵盤(pán)輸入焦點(diǎn)的元素添加樣式

          :hover 當(dāng)鼠標(biāo)懸浮在元素上方時(shí),向元素添加樣式

          :link 向未被訪問(wèn)的鏈接添加樣式

          :visited 向已被訪問(wèn)的鏈接添加樣式

          :first-child 向元素的第一個(gè)子元素添加樣式(不建議使用)

          :lang 向帶有指定lang屬性的元素添加樣式

          9、偽元素:用于向某些選擇器設(shè)置特殊效果。

          :first-letter 向文本的第一個(gè)字母添加樣式

          :first-line 向文本的首行添加樣式

          :before 在元素之前添加內(nèi)容

          :after 在元素之后添加內(nèi)容

          4.3、常用CSS屬性

          前端撕出逼格,撕的硬氣 - 產(chǎn)品應(yīng)該懂的html/css基礎(chǔ)知識(shí)

          debug-wang 融入網(wǎng)絡(luò) PM 1小時(shí)前

          之前和前端交流頁(yè)面的實(shí)現(xiàn)方案時(shí),經(jīng)常被告知:這個(gè)效果實(shí)現(xiàn)不了;那個(gè)東西兼容性不好;這個(gè)做不了...明明這些效果別人家已經(jīng)實(shí)現(xiàn)出來(lái)了,哎,奈何不懂前端相關(guān),沒(méi)轍!

          最近花了點(diǎn)時(shí)間看了些前端相關(guān)的博客、論壇,整理了一些html/css的基礎(chǔ)知識(shí),算是學(xué)個(gè)入門(mén)。同時(shí)還學(xué)了瀏覽器調(diào)試工具的基本用法,做產(chǎn)品測(cè)試的時(shí)候還是蠻有用的,谷歌瀏覽器自帶的調(diào)試工具很好用,火狐的話要裝firebug,有興趣的可以去玩玩,還是很有意思的!

          # 基本

          html(Hypertext Markup Language)—— 結(jié)構(gòu) 超文本標(biāo)記語(yǔ)言

          css(Cascading Style Sheets)—— 樣式 層疊樣式表

          js(javascript)—— 行為

          html超文本標(biāo)記語(yǔ)言<html> </html> 標(biāo)簽對(duì)

          <!DOCTYPE HTML> 聲明文檔類型

          <meta charset="utf-8"/> 代碼編碼格式

          單標(biāo)簽:直接在后面斜杠結(jié)束的標(biāo)簽叫做單標(biāo)簽。如換行符<br />

          行間樣式表<div style="……"></div>

          內(nèi)部樣式表<style>…………</style>

          外部樣式表<link href="style.css" rel="stylesheet"/>

          # 屬性:屬性值;

          width 寬度height 高度

          background 背景

          background-attachment: fixed; 背景是否滾動(dòng)

          background-color: gray; 背景顏色

          background-image: url(bg.jpg); 背景圖

          background-repeat: no-repeat; 背景圖是否重復(fù)

          background-position: center 0px; 背景圖位置

          # 傳說(shuō)中的盒模型

          盒子大小 = border + padding + width/height

          盒子寬度 = 左border+左padding+width+右padding +右border

          盒子高度 = 上border+上padding+height+下padding+下border

          # 基礎(chǔ)屬性

          width 寬度 height 高度

          background 背景 border 邊框

          padding 內(nèi)邊距 margin 外邊距

          font-size 文字大小 font-family 字體

          color 文字顏色 line-height 行高

          text-align 文本對(duì)齊方式 text-indent 首行縮進(jìn)

          font-weight 文字著重 font-style 文字樣式

          text-decoration 文本修飾 letter-spacing 字母間距

          word-spacing 單詞間距

          # 基礎(chǔ)標(biāo)簽

          div 塊

          img 圖片(單標(biāo)簽)

          a 鏈接、下載、錨點(diǎn)

          h1-h6 標(biāo)題

          p 段落

          strong 強(qiáng)調(diào)(粗體)

          em 強(qiáng)調(diào)(斜體)

          span 區(qū)分樣式

          ul 無(wú)序列表

          ol 有序列表

          li 列表項(xiàng)

          dl 定義列表

          dt 定義列表標(biāo)題

          dd 定義列表項(xiàng)

          # CSS基礎(chǔ)選擇符

          id選擇符(#)

          群組選擇符(,)

          class選擇符(.)

          類型選擇符(div……)

          包含選擇符(div p)

          通配符(*)

          基礎(chǔ)選擇符的優(yōu)先級(jí)

          類型 < class < id < style(行間) < js

          偽類:偽類用于向被選中元素添加特殊的效果。(元素在特定情況下才具備的。)

          a : link 未訪問(wèn)(默認(rèn))

          a : hover 鼠標(biāo)懸停(鼠標(biāo)劃過(guò))

          a : active 鏈接激活(鼠標(biāo)按下)

          a : visited 訪問(wèn)過(guò)后(點(diǎn)擊過(guò)后)

          a標(biāo)簽四個(gè)偽類的順序:

          link visited hover active

          (love hate 記憶方法!)

          a偽類的應(yīng)用:

          a、四個(gè)偽類全用(搜索引擎、新聞門(mén)戶、小說(shuō)網(wǎng)站)

          b、一般網(wǎng)站只用( a{} a:hover{} )

          #(樣式重置)css reset 原則

          但凡是瀏覽默認(rèn)的樣式,都不要使用。頁(yè)面在不同瀏覽器下總是不能做到很好的兼容,顯示的效果不同,估計(jì)就是一開(kāi)始沒(méi)有做樣式重置!

          <style>

          body,dl,dd,p,h1,h2,h3,h4,h5,h6{margin:0;font-size:12px;}

          ol,ul{margin:0;padding:0;list-style:none;}

          a{text-decoration:none;}

          img{border:none;}

          </style>

          # float浮動(dòng):

          1、塊在一排顯示

          2、內(nèi)聯(lián)支持寬高

          3、默認(rèn)內(nèi)容撐開(kāi)寬度

          4、脫離文檔流

          5、提升層級(jí)半層

          float:left | right | none | inherit;

          文檔流是文檔中可顯示對(duì)象在排列時(shí)所占用的位置。

          浮動(dòng)的定義:使元素脫離文檔流,按照指定方向發(fā)生移動(dòng),遇到父級(jí)邊界或者相鄰的浮動(dòng)元素停了下來(lái)。

          clear:left | right | both | none | inherit;元素的某個(gè)方向上不能有浮動(dòng)元素clear:both; 在左右兩側(cè)均不允許浮動(dòng)元素。

          # 清浮動(dòng)方法

          1.加高問(wèn)題:擴(kuò)展性不好

          2.父級(jí)浮動(dòng)問(wèn)題:頁(yè)面中所有元素都加浮動(dòng),margin左右自動(dòng)失效(floats bad ?。?/p>

          3.inline-block 清浮動(dòng)方法:?jiǎn)栴}:margin左右自動(dòng)失效;

          4.空標(biāo)簽清浮動(dòng)問(wèn)題:IE6 最小高度 19px;(解決后IE6下還有2px偏差)

          5.br清浮動(dòng)問(wèn)題:不符合工作中:結(jié)構(gòu)、樣式、行為,三者分離的要求。

          6.after偽類 清浮動(dòng)方法(現(xiàn)在主流方法)

          .clear:after{content:'';display:block;clear:both;}

          .clear{zoom:1;}

          after偽類: 元素內(nèi)部末尾添加內(nèi)容;

          :after{content"添加的內(nèi)容";} IE6,7下不兼容

          zoom 縮放

          a、觸發(fā) IE下 haslayout,使元素根據(jù)自身內(nèi)容計(jì)算寬高。

          b、FF 不支持;

          7.overflow:hidden 清浮動(dòng)方法; 問(wèn)題:需要配合 寬度 或者 zoom 兼容IE6 IE7;

          overflow: scroll | auto | hidden;

          overflow:hidden;溢出隱藏(裁刀?。?/p>

          # 浮動(dòng)兼容性問(wèn)題

          IE6雙邊距BUG(IE6下塊屬性標(biāo)簽浮動(dòng),并且有橫向margin,橫向margin加倍。

          a、IE6

          b、浮動(dòng)

          c、橫向margin

          d、塊屬性標(biāo)簽(加display:inline;)

          IE6下 li部分兼容性問(wèn)題:

          a、左右兩列布局,右邊右浮動(dòng)IE6 IE7下折行;(左邊元素浮動(dòng))b、IE6 IE7 li 下元素都浮動(dòng) 在IE6 IE7下 li 下方會(huì)產(chǎn)生4px間隙問(wèn)題;(加vertical-align:top;)

          # 定位

          position:relative; 相對(duì)定位

          a、不影響元素本身的特性;

          b、不使元素脫離文檔流;

          c、如果沒(méi)有定位偏移量,對(duì)元素本身沒(méi)有任何影響;

          定位元素位置控制 top/right/bottom/left 定位元素偏移量。

          position:absolute; 絕對(duì)定位

          a、使元素完全脫離文檔流;

          b、使內(nèi)嵌支持寬高;

          c、塊屬性標(biāo)簽內(nèi)容撐開(kāi)寬度;

          d、如果有定位父級(jí)相對(duì)于定位父級(jí)發(fā)生偏移,沒(méi)有定位父級(jí)相對(duì)于整個(gè)文檔發(fā)生偏移;

          e、相對(duì)定位一般都是配合絕對(duì)定位元素使用;

          z-index:[number]; 定位層級(jí) a、定位元素默認(rèn)后者層級(jí)高于前者;

          position:fixed; 固定定位

          與絕對(duì)定位的特性基本一致,的差別是始終相對(duì)整個(gè)文檔進(jìn)行定位;

          問(wèn)題:IE6不支持固定定位;

          定位其他值:

          position:static ; 默認(rèn)值

          position:inherit ; 從父元素繼承定位屬性的值

          position:relative | absolute | fixed | static | inherit;

          position:relative;在 IE6 下父級(jí)的 overflow:hidden; 包不住子級(jí)的relative;

          position:absolute;在 IE6 下定位元素的父級(jí)寬高都為奇數(shù)那么在 IE6 下定位元素的 right 和 bottom 都有1像素的偏差。

          position:absolute; 絕對(duì)定位元素子級(jí)的浮動(dòng)可以不用寫(xiě)清浮動(dòng)方法;

          position:fixed; 固定定位元素子級(jí)的浮動(dòng)可以不用寫(xiě)清浮動(dòng)方法;(IE6不兼容)

          # 表格標(biāo)簽

          table 表格

          thead 表格頭

          tbody 表格主體

          tfoot 表格尾

          tr 表格行

          th 元素定義表頭

          td 元素定義表格單元

          colspan 屬性規(guī)定單元格可橫跨的列數(shù)。<td colspan="2"></td>rowspan 屬性規(guī)定單元格可橫跨的行數(shù)。<td rowspan="2"></td>

          # form 表單

          <input type="…… " name="" value="" />

          text 文本框

          password 密碼

          radio 單選

          checkbox 復(fù)選

          submit 提交

          reset 重置

          button 按鈕

          image 圖片

          file 上傳

          hidden 隱藏

          # 滑動(dòng)門(mén)

          滑動(dòng)門(mén)并不是一項(xiàng)全新的技術(shù),它是利用背景圖像的可層疊性,并允許他們?cè)诒舜酥线M(jìn)行滑動(dòng),以創(chuàng)造一些特殊的效果。

          CSS Sprites在國(guó)內(nèi)很多人叫CSS精靈,是一種網(wǎng)頁(yè)圖片應(yīng)用處理方式。它允許你將一個(gè)頁(yè)面涉及到的所有零星圖片都包含到一張大圖中去。

          CSS Sprites并不是一門(mén)新技術(shù),目前它已經(jīng)在網(wǎng)頁(yè)開(kāi)發(fā)中發(fā)展得十分成熟。大部分公司要求前端工程師必須使用CSS 精靈,處理圖片;

          CSS精靈 優(yōu)點(diǎn):利用CSS 精靈能很好地減少了網(wǎng)頁(yè)的http請(qǐng)求次數(shù),從而大大的提高了頁(yè)面的性能,這也是CSS 精靈最大的優(yōu)點(diǎn);減少圖片大小

          CSS精靈 缺點(diǎn):

          降低開(kāi)發(fā)效率;

          維護(hù)難度加大;

          # # 立志要搞懂技術(shù)的產(chǎn)品小白,正在學(xué)習(xí)JQuery的路上……

          出于對(duì)PMCAFF用戶的尊重,任何在PMCAFF產(chǎn)品經(jīng)理社區(qū)發(fā)布的內(nèi)容,在未經(jīng)允許的情況下,不得在任何平臺(tái)被直接或間接發(fā)布使用或被用于其他任何商業(yè)目的。如有違反上述聲明者本網(wǎng)站將追究其相關(guān)法律責(zé)任。

          微信公眾號(hào):pmcaffcom

          投稿郵箱:tougao@pmcaff.com

          Greated by PMCAFF產(chǎn)品經(jīng)理社區(qū) - www.pmcaff.com


          主站蜘蛛池模板: 久久人做人爽一区二区三区| 精品人妻一区二区三区毛片| 国产在线一区二区杨幂| 亚洲无码一区二区三区| 熟妇人妻系列av无码一区二区| 亚洲一区二区久久| 中文字幕亚洲一区二区三区| 3d动漫精品啪啪一区二区中文| 国产精品乱码一区二区三区 | 农村乱人伦一区二区| 免费人人潮人人爽一区二区| 中文字幕乱码一区久久麻豆樱花 | 亚洲一区影音先锋色资源| 爆乳熟妇一区二区三区| 国产综合视频在线观看一区 | 波多野结衣高清一区二区三区| 国模视频一区二区| 日韩成人无码一区二区三区| 亚欧免费视频一区二区三区| 久久一区二区精品综合| 国产成人一区二区三区视频免费| 日本一区二区三区爆乳| 国产一区二区三区不卡AV| 精品视频一区二区三区四区五区| 无码少妇一区二区浪潮av| 国产一区二区三区精品久久呦| 日韩aⅴ人妻无码一区二区| 亚洲AV日韩综合一区| 免费高清在线影片一区| 在线观看日韩一区| 亚洲国产精品一区二区九九| 内射白浆一区二区在线观看| 无码精品人妻一区二区三区免费| 一区二区视频免费观看| 一区二区视频免费观看| 国产美女精品一区二区三区| 中文字幕精品一区二区日本| 中文字幕在线看视频一区二区三区| 精品国产一区二区三区不卡| 亚洲中文字幕一区精品自拍| 精品久久综合一区二区|