TypeScript開發環境搭建(VSCode+NodeJs)自動建立
TypeScript開發環境搭建(VSCode+NodeJs)自動建立
如果只是使用VS Code + Node.js來編寫TypeScript程式碼,可能需要以下的命令:
npm init -y npm install typescript --save-dev npm install @types/node --save-dev npx tsc --init --rootDir src --outDir lib --esModuleInterop --resolveJsonModule --lib es6,dom --module commonjs npm install --save-dev ts-node npm install --save-dev nodemon
並像下面這樣配置package.json檔案,才能完成開發環境的搭建。
"scripts": {
"start": "npm run build:live",
"build": "tsc -p .",
"build:live": "nodemon --watch src/**/*.ts --exec ts-node src/index.ts",
"all": "start & build"
},
每次都做這些配置就是是不是很麻煩?那麼怎麼解決?編寫Node.js CLI程式,這樣每次建立同樣的目錄結構和檔案時,只需要在命令列輸入命令即可。
建立Node.js CLI
-
新建資料夾mycli。
-
在新建的資料夾下開啟命令列,執行命令npm init -y以建立package.json檔案。按以下方式配置bin欄位。配置後才能在控制檯使用自己建立的命令。
"bin": {
"mycli": "./index.js"
}
- 在當前目錄(即mycli)下建立index.js檔案。這是命令要執行的檔案。
- 在檔案首行寫“#!Node”,表示使用node來執行該檔案。(Windows系統)
- 在檔案首行寫“#!/usr/bin/env node”。(Linux/Unix系統)
- 在index.js中寫上console.log("Hello World!");以展示執行效果。用實際程式碼替換掉即可。(之後要將這裡的程式碼替換為建立目錄及檔案的程式碼。)
- 此時,已完成命令列的編寫。只需將當前專案到全域性環境,然後就可使用“mycli”命令了。全域性安裝。在當前目錄(即mycli)下執行以下命令以安裝。
npm install -g
-
使用:新建一個目錄,並執行命令列,在命令列中執行mycli命令。即可看到效果。
-
例子:在index.js中寫上建立目錄及檔案的程式碼以快速生成專案目錄結構及檔案。
參考資料:使用Node.js編寫CLI工具
流程
- 輸入專案型別及專案根目錄。
- 根據目錄(及檔案)結構的模板建立資料夾和檔案。
- 輸出反饋資訊。
程式碼
建立package.json
- 建立資料夾MakeProject。
- 執行VS Code,通過快捷鍵Ctrl + ~ 開啟終端。在終端中執行命令npm init -y建立package.json。
- 配置package.json如下:
{
"name": "mkproject",
"version": "1.0.0",
"description": "Create node or typescript project automatically",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"bin": {
"mkproject": "./index.js"
},
"author": "",
"license": "ISC"
}
"name"是專案名稱。
"version"是專案版本。
"description"是專案的描述。
"scripts"是npm命令指令碼,如果改為上面那樣,則在終端中可以通過npm run start來執行這個名為start的指令碼。
name、version、description和scripts可改可不改。
bin這裡一定要修改。mkproject就是之後要使用的node.js cli命令。喜歡的華可以改成自己的。
鍵盤輸入
- 在MakeProject資料夾下建立lib資料夾。然後建立console.js檔案。
- 在Node.js中從命令列接收輸入。從命令列讀取輸入是非同步地,為了讓非同步操作能夠像同步操作一樣按順序執行這裡使用了Promise。
程式碼如下:參考文件
const readline = require('readline');
/**
* 提示使用者在客戶端進行輸入。
* @param {string} prompt 提示使用者進行輸入時,需要顯示的資訊。
* @author kokiafan
* @example
* const readline = require('./lib/console');
*
* async function main() {
* let msg = await readline("請輸入一行文字:");
* console.log("剛剛輸入的資訊:%s", msg);
* }
*
* main();
*/
function readlineAsync(prompt = "Please enter: ") {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise(resolve => {
rl.question(prompt, (data) => {
rl.close();
resolve(data);
})
});
};
module.exports = { readlineAsync };
上面的函式頭有JSDoc註釋。這個可以給VS Code的Intellisence提供提示資訊,有助於呼叫該函式的程式設計師使用。參考資料如下:
參考資料
JSDoc的使用:https://www.jianshu.com/p/46519b0499c3
JSDoc的文件:https://www.html.cn/doc/jsdoc/tags-example.html
檔案建立
- 在lib資料夾下建立console.js檔案。
- 程式碼如下:
const fs = require('fs');
/**
* 傳入一個目錄名稱字串陣列及根目錄名稱,非同步地建立資料夾。
* @param {string[]} directories
* @param {string} root
* @author kokiafan
* @example
* const io = require('./lib/io');
*
* async function main() {
* io.createDirectories(['dist/css', 'src/css', 'src/html'], './myapps')
* .then(() => console.log('目錄建立完成!'));
* }
*
* main();
*/
function createDirectories(directories, root) {
return new Promise(resolve => {
for (let dir of directories) {
let path = root + '/' + dir;
fs.mkdirSync(path, { recursive: true }, error => {
if (error) {
console.log(error);
}
});
console.log(path);
}
resolve(root);
});
}
/**
* 傳入一個目錄名稱字串陣列及根目錄名稱,非同步地建立資料夾。
* @param {{filename:string, content: string}[]} files
* @param {string} root
* @author kokiafan
* @example
* const io = require('./lib/io');
*
* const file1 = { filename: 'text1.txt', content: "hello" };
* const file2 = { filename: 'text2.txt', content: "hello" };
*
* async function main() {
* io.createFiles([file1, file2], '.')
* .then(() => console.log('檔案建立完成!'));
* }
*
* main();
*/
function createFiles(files, root) {
return new Promise(resolve => {
for (let file of files) {
let path = root + '/' + file.filename;
fs.writeFile(path, file.content, 'utf8', error => {
if (error) {
console.log(error);
}
});
console.log(path);
}
resolve(root);
});
}
module.exports = {
createDirectories,
createFiles
}
資料準備
- 簡單的TypeScript專案:
- 需要建立的資料夾及檔案:MakeProject\data\ts\dirs\dirs.js,MakeProject\data\ts\files\files.js,package_json.js,src_index_ts.js,tsconfig_json.js。
- 程式碼:
dirs.js
// 需要被建立的資料夾
module.exports = ['src'];
files.js
const package_json = require('./package_json');
const tsconfig_json = require('./tsconfig_json');
const index_ts = require('./src_index_ts');
// 需要被建立的檔案
const files = [
{ filename: package_json.filename, content: package_json.content },
{ filename: tsconfig_json.filename, content: tsconfig_json.content },
{ filename: index_ts.filename, content: index_ts.content }
];
module.exports = files;
package_json.js
// 宣告需要被建立的檔案及其內容
const package_json = {
filename: "package.json",
content:
`
{
"name": "ts_app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "npm run build:live",
"build": "tsc",
"build:live": "nodemon --watch src/**/*.ts --exec ts-node src/index.ts"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@types/node": "^14.14.16",
"nodemon": "^2.0.6",
"ts-node": "^9.1.1",
"typescript": "^4.1.3"
}
}
`
};
module.exports = package_json;
src_index_ts.js
// 宣告需要被建立的檔案及其內容
const src_index_ts = {
filename: "src/index.ts",
content:
`
import * as fs from "fs";
const path = "./message.txt";
const data = "Hello World!";
const encoding = "utf8";
console.log(data);
fs.writeFile(path, data, encoding, error => {
if (error) {
console.log(error);
}
});
`
};
module.exports = src_index_ts;
tsconfig_json.js
// 宣告需要被建立的檔案及其內容
const tsconfig_json = {
filename: "tsconfig.json",
content:
`
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
"lib": [
"es6",
"dom"
], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "lib", /* Redirect output structure to the directory. */
"rootDir": "src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
"resolveJsonModule": true, /* Include modules imported with '.json' extension */
"skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules"
]
}
`
};
module.exports = tsconfig_json;
- JavaScript Mvc專案:
- 需要建立的資料夾及檔案:MakeProject\data\jsmvc\dirs\dirs.js,MakeProject\data\jsmvc\files\files.js,package_json.js,index_js.js。
- 程式碼:
dirs.js
// 需要被建立的資料夾
module.exports = ['public', 'models', 'views', 'controllers', 'middleware'];
files.js
const package_json = require('./package_json');
const index_js = require('./index_js');
// 需要被建立的檔案
const files = [
{ filename: package_json.filename, content: package_json.content },
{ filename: index_js.filename, content: index_js.content }
];
module.exports = files;
package_json.js
// 宣告需要被建立的檔案及其內容
const package_json = {
filename: "package.json",
content:
`{
"name": "nodejs",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node index.js",
"test": "echo \\"Error: no test specified\\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.17.1"
}
}`
};
module.exports = package_json;
index_js.js
const index_js = {
filename: "index.js",
content:
`
const express = require('express');
const app = express();
const port = process.env.port || 3000;
app.get('/', (request, response) => {
response.send('Hello World');
});
app.listen(port, () => {
console.log(\`Express web app available at localhost: \${port}\`);
});`
}
module.exports = index_js;
- TypeScript專案(VSCode配置 + TypeScript + WebPack + WebPack-Cli + html-webpack + ts-loader + lite-server):
-
需要建立的資料夾及檔案:MakeProject\data\tswebpack\dirs\dirs.js,MakeProject\data\tswebpack\files\files.babelrs.js,
, bs_config_json.js,
,dist_css_banner_css.js
,package_json.js
,src_css_banner_css.js
,src_html_index_html.js
,src_ts_app_ts.js
,src_ts_helloworld_ts.js
,src_ts_index_ts.js
,src_ts_slider_ts.js
,src_ts_sum_ts.js
,tsconfig_json.js
,vscode_launch_json.js
,vscode_taskss_json.js
,webpack_config_js.js -
程式碼:
dirs.js
// 需要被建立的資料夾
module.exports = ['.vscode', 'dist/css', 'src/css', 'src/html', 'src/ts'];
其餘的略:
組裝程式
將上面的console.js、io.js和data資料夾裡的資料組合到程式中,完成整個程式。在MakeProject資料夾下建立index.js檔案。程式碼如下:
#!Node
// #!/usr/bin/env node /linux下這樣寫
const consoleio = require('./lib/console');
const fsio = require('./lib/io');
/**
* 輸入專案型別的字串,返回專案所需要的資料夾及檔案。
* @param {'ts'|'jsmvc'|'tswebpack'} projectType 使用者想要的專案型別。
* @return { dirs: string[], files: { filename: string, content: string }[] }
* @author kokiafan
* @example
* let projectType = select('ts');
*/
function select(projectType) {
if (projectType === 'ts') {
return {
dirs: require('./data/ts/dirs/dirs'),
files: require('./data/ts/files/files')
};
}
if (projectType === 'jsmvc') {
return {
dirs: require('./data/jsmvc/dirs/dirs'),
files: require('./data/jsmvc/files/files')
};
}
if (projectType === 'tswebpack') {
return {
dirs: require('./data/tswebpack/dirs/dirs'),
files: require('./data/tswebpack/files/files')
};
}
return null;
}
async function main() {
let root = await consoleio.readlineAsync("請輸入專案根目錄:");
let type = await consoleio.readlineAsync("請輸入專案的型別:(選項ts|jsmvc|tswebpack)");
let projectType = select(type);
if (projectType === null) {
console.log("專案型別錯誤。")
return;
}
fsio.createDirectories(projectType.dirs, root)
.then(() => fsio.createFiles(projectType.files, root));
}
main();
至此,程式寫完。在終端中輸入命令npm install -g安裝該專案後,即可使用mkproject來建立專案了。