react+ts搭建前端工程


前言

此文為ssr三部曲的第一部,前文在這
這個版本的代碼在這

安裝依賴

typescript

安裝typescript,並初始化一個tsconfig.json出來

npm install -S -D typescript
node_modules/.bin/tsc --init    // 局部tsc需要這樣使用

babel7開始,新增了@babel/preset-typescript,支持解析ts,所以不再需要ts-loader之類的webpack loader了

webpack

安裝webpack、webpack-cli以及webpack-dev-server,webpack4開始webpack-cli與webpack分離成2個包了,分開維護。
webpack-dev-server開發用,原理是用express起一個服務器,文件被build到內存中,通過socket跟client連接,達到熱更新的目的。

npm install -S -D webpack webpack-cli webpack-dev-server

react

安裝react以及react-dom

npm i -S react react-dom

再安裝一下聲明文件

npm i -D @types/react @types/react-dom

css

css擴展是不可缺少的,我用sass最多,所以先安裝sass相關依賴:

npm install -S -D sass sass-loader css-loader style-loader mini-css-extract-plugin postcss-loader postcss-import postcss-preset-env postcss-pxtorem cssnano
  • sass即為dart-sass,以前一般使用node-sass,安裝中可能碰到各種問題,dart-sass能完美兼容並且避免這些問題
  • sass-loader用來把sass翻譯成css
  • css-loader用來讀取css文件(僅讀取)
  • style-loader會把css用style標簽包起來,放到header中,適用devlopment環境
  • mini-css-extract-plugin與style-loader作用類似,區別是使用link標簽作為獨立的文件引入,適用production環境
  • postcss-loader用來添加瀏覽器css兼容性代碼

后面4個都是postcss的插件,作用如下:

  • postcss-import用來處理css中的@import指令
  • postcss-preset-env跟babel類似,把新css語法轉換為舊css語法
  • postcss-px2rem用來把px自動轉換成rem
  • cssnano用來壓縮代碼

babel

npm install -D babel-loader @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript @babel/plugin-proposal-class-properties @babel/plugin-proposal-object-rest-spread @babel/plugin-syntax-dynamic-import
  • babel-loader和@babel/core就不用說了
  • preset-xxx(preset-env、preset-react、preset-typescript)都是babel預設的一些轉碼規則集,這3個分別用來轉碼es、react、ts

    preset的順序為從后至前

  • @babel/plugin-xxx 都是babel的插件,會在preset之前執行,preset不支持的一些特性,就需要導入plugin了,這3個分別用來支持轉碼class、擴展運算符、動態導入

    plugin的順序為從前至后

此處必須提一下@babel/plugin-transform-runtime,runtime是提供沙箱墊片的方式來轉換代碼,而preset-env會覆蓋全局環境,更具體的區別可以自行google:

eslint、prettier

安裝eslint及prettier:

npm install -D eslint prettier eslint-plugin-prettier eslint-config-prettier eslint-plugin-react  

因為是react項目,所以除了eslint和prettier還裝了eslint-plugin-react

新增.eslintrc,填入以下內容:

{
    "extends": [
        "plugin:prettier/recommended"
    ]
}

eslint和prettier此處不做具體展開,請自行google,僅稍微說明一下eslint:
eslint里有config和plugin兩個概念,config配置規則(指定plugin對應的規則),plugin則定義具體的規則校驗邏輯。
所以eslint-plugin-prettier默認的配置是要定義plugin以及rule,里面嵌套的recommended則直接定義了config,所以直接可以把plugin:prettier/recommended扔到extends中來使用

上面配置之后,你會發現寫ts,會提示各種語法錯誤,顯然不識別ts的語法,所以我們還需要添加ts的支持:

npm i -D @typescript-eslint/parser @typescript-eslint/eslint-plugin

最終的.eslintrc如下:

{
    "parser": "@typescript-eslint/parser",
    "parserOptions": {
        "ecmaVersion": 2020, // Allows for the parsing of modern ECMAScript features
        "sourceType": "module", // Allows for the use of imports
        "ecmaFeatures": {
            "jsx": true
        }
    },
    "plugins": [
        "@typescript-eslint"
    ],
    "extends": [
        "plugin:react/recommended",
        "plugin:@typescript-eslint/recommended",
        "prettier/@typescript-eslint",
        "plugin:prettier/recommended"
    ],
    "env": {
        "browser": true,
        "node": true
    }
}

更完善的格式化流程,還要配置husky和lint-staged,此處不做展開,可參考以下兩篇文章:robertcooper掘金
有沒有感覺很頭大,這么多東西要配置。。

其他loader、plugin

npm install -S -D url-loader html-webpack-plugin case-sensitive-paths-webpack-plugin clean-webpack-plugin
  • url-loader用來處理其他文件的import/require,例如圖片(把這些文件轉發到生成目錄,並解析成對應的url)

    url-loader內置了file-loader,可以設置指定大小以下文件轉為DataURI

  • html-webpack-plugin用來生成一個html文件
  • case-sensitive-paths-webpack-plugin用來做路徑大小寫嚴格判斷(mac上大小寫不敏感,window和linux大小寫敏感)
  • clean-webpack-plugin用來在每次打包之前清空目標目錄(webpack的默認行為是增量,不清空)

開發調試

開發環境我們需要熱更新,需要修改代碼自動保存,這就需要用到我們之前安裝的webpack-dev-server了,按照文檔描述,配置devServer即可

最終結果,截止到目前為止,webpack的全部配置為以下兩個文件:

  • module-rules.js

    loaders較多,所以單獨寫了一個文件

const isDev = process.env.NODE_ENV === 'development';
const miniCssExtract = require('mini-css-extract-plugin');
const postcssImport = require('postcss-import');
const postcssPresetEnv = require('postcss-preset-env');
const cssnano = require('cssnano');
const pix2rem = require('postcss-pxtorem');
const sass = require('sass');

const cssLoaders = [
    isDev ? 'style-loader' : miniCssExtract.loader,
    'css-loader',
    {
        loader: 'postcss-loader',
        options: {
            ident: 'postcss',
            plugins: (loader) => {
                const targetPlugins = [
                    postcssImport({ root: loader.resourcePath }),
                    pix2rem({ propList: ['*'], rootValue: 100 }),
                    postcssPresetEnv(),
                ]

                if (!isDev) {
                    targetPlugins.push(cssnano());
                }

                return targetPlugins;
            }
        },
    },
]

module.exports = () => {
    return [
        {
            test: /\.(js|jsx|ts|tsx)$/,
            exclude: /mode_modules/,
            use: [
                {
                    loader: 'babel-loader',
                    options: {
                        cacheDirectory: true,
                    }
                }
            ]
        },
        {
            test: /\.css$/,
            use: cssLoaders,
        },
        {
            test: /\.scss$/,
            use: [
                ...cssLoaders,
                {
                    loader: 'sass-loader',
                    options: {
                        implementation: sass,
                    }
                }
            ],
        },
        {
            test: /\.(png|jpg|jpeg|gif|svg|ttf)$/,
            exclude: /node_modules/,
            use: [
                {
                    loader: 'url-loader',
                    options: {
                        limit: 5*1024,  // 5kb
                    },
                }
            ],
        }
    ]
}
  • webpack.client.js
const webpack = require('webpack');
const path = require('path');
const cwd = process.cwd();
const fs = require('fs');
const moduleRules = require('./module-rules');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const CaseSensitivePathPlugin = require('case-sensitive-paths-webpack-plugin');

const isDev = process.env.NODE_ENV === 'development';
console.log('env is: '+process.env.NODE_ENV);

const plugins = [
    new webpack.ProgressPlugin(),
    new webpack.DefinePlugin({
        'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
        'process.env.DEPLOY_ENV': JSON.stringify(process.env.DEPLOY_ENV),
    }),
    new CleanWebpackPlugin(),
    new CaseSensitivePathPlugin(),
    new HtmlWebpackPlugin({
        template: path.resolve(cwd, 'webpack/index.html'),
        filename: 'index.html',
    }),
];
if(isDev){
    plugins.push(new webpack.HotModuleReplacementPlugin());
    plugins.push(new webpack.NamedModulesPlugin());
}


module.exports = {
    entry: path.resolve(cwd, 'src/client/app'),
    output: {
        path: path.resolve(cwd, 'dist/client'),
        filename: isDev ? 'js/[name].[hash].js': 'js/[name].[contentHash].js',
        chunkFilename: isDev ? 'chunks/[name].[hash].js' : 'chunks/[name].[contentHash].js',
        publicPath: '/',
    },
    // mode: process.env.NODE_ENV,  // 由 --mode參數指定
    resolve: {
        extensions: ['.ts', '.tsx', '.scss', '.js', '.jsx', '.sass'],
        alias: {
            "@client": path.resolve(cwd, 'src/client'),
            "@server": path.resolve(cwd, 'src/server'),
        }
    },
    module: {
        rules: moduleRules(),
    },
    plugins,
    watch: isDev,
    devServer: isDev ? {
        contentBase: path.resolve(cwd, 'src/client'),
        compress: true,
        host: '0.0.0.0',
        port: 8080,
        hot: true,
        open: true,
        watchOptions: {
            ignored: /node_modules/,    // 監聽過多文件會占用cpu、內存,so,可以忽略掉部分文件
            aggregateTimeout: 200,  // 默認200,文件變更后延時多久rebuild
            poll: false,    // 默認false,如果不采用watch,那么可以采用poll(輪詢)
        },
    } : undefined,
    devtool: isDev ? "inline-source-map": undefined,
};
  • package.json中添加了以下兩個命令:
{
    "dev": "cross-env NODE_ENV=development DEPLOY_ENV=dev webpack-dev-server --mode=development --config webpack/webpack.client.js",
    "prd": "cross-env NODE_ENV=production DEPLOY_ENV=prd node_modules/.bin/webpack --mode=production --config webpack/webpack.client.js",
}

OK,開發和生產打包就都已經搞定了

小結

這里是配置react,vue本質上也是一樣的(webpack的核心、基礎配置是不變的),換上vue相關的webpack loader,vue相關的eslint插件,等等。

從0配置spa前端項目,到這里這就已經完成了,整體來看,配置的東西的確很多,但也都是作為工程化所需要的東西。
說到底,也是因為前端沒有一個統一的、完善的環境工具,對於后端來說,比方說.net,一個visual studio搞定一切,美滋滋,哪有這么多事。。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM