1. 程式人生 > 實用技巧 >TypeScript開發環境搭建(VSCode+NodeJs)自動建立

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

  1. 新建資料夾mycli。

  2. 在新建的資料夾下開啟命令列,執行命令npm init -y以建立package.json檔案。按以下方式配置bin欄位。配置後才能在控制檯使用自己建立的命令。

"bin": {
    "mycli": "./index.js"
}
  1. 在當前目錄(即mycli)下建立index.js檔案。這是命令要執行的檔案。
  • 在檔案首行寫“#!Node”,表示使用node來執行該檔案。(Windows系統)
  • 在檔案首行寫“#!/usr/bin/env node”。(Linux/Unix系統)
  • 在index.js中寫上console.log("Hello World!");以展示執行效果。用實際程式碼替換掉即可。(之後要將這裡的程式碼替換為建立目錄及檔案的程式碼。)
  1. 此時,已完成命令列的編寫。只需將當前專案到全域性環境,然後就可使用“mycli”命令了。全域性安裝。在當前目錄(即mycli)下執行以下命令以安裝。
npm install -g
  1. 使用:新建一個目錄,並執行命令列,在命令列中執行mycli命令。即可看到效果。

  2. 例子:在index.js中寫上建立目錄及檔案的程式碼以快速生成專案目錄結構及檔案。

參考資料:使用Node.js編寫CLI工具

流程

  1. 輸入專案型別及專案根目錄。
  2. 根據目錄(及檔案)結構的模板建立資料夾和檔案。
  3. 輸出反饋資訊。

程式碼

建立package.json

  1. 建立資料夾MakeProject。
  2. 執行VS Code,通過快捷鍵Ctrl + ~ 開啟終端。在終端中執行命令npm init -y建立package.json。
  3. 配置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命令。喜歡的華可以改成自己的。

鍵盤輸入

  1. 在MakeProject資料夾下建立lib資料夾。然後建立console.js檔案。
  2. 在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

檔案建立

  1. 在lib資料夾下建立console.js檔案。
  2. 程式碼如下:
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
}

資料準備

  1. 簡單的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;
  1. 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;
  1. 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來建立專案了。

程式碼庫:https://github.com/kokiafan/makeProject