開發一個基於react & typescript 的npm ui組件包

npm 註冊登陸

  • 前置條件: 切到對應的npm源
  1. npm logout
  2. npm login
  3. 依次輸入帳號、密碼、郵箱
  4. npm publish (會提示去npm官網驗證郵箱地址)
  • npm 發佈時可能遇到的問題
    1. 源出錯
    2. 包名重複
    3. 每次發佈前要修改package.json的版本號,必需要大於上一次的版本號
  • npm link 本地調試:爲調試帶來的頻繁發包,能夠使用 npm link 將npm包代理到本地調試,操做步驟:
    1. 進入源碼目錄執行 npm link
    2. 進入使用目錄即示例代碼執行 npm link [包名],折後就能夠直接在示例代碼處使用 import xxx from 'xxx' 進行調試了

webpack ts babel 等打包配置文件書寫

參照這篇文章寫的挺全面的,只不過它沒有引入typescriptcss

  • 本文寫時 "webpack": "^4.41.6",,下面把主要流程記錄一下最終完成的目錄結構以下所示
    |____babelrc // babel 配置
    |____config  // webpack配置
        ├── webpack.base.js // 公共配置
        ├── webpack.dev.config.js // 開發環境配置
        └── webpack.prod.config.js // 打包發佈環境配置
    |____example // 開發環境調試目錄
    |____node_modules 
    |____README.md
    |____yarn.lock
    |____public // 開發調試環境的模板 index.html
    |____.gitignore
    |____package.json
    |____lib // 打包後目錄
    |____tsconfig.json // ts配置
    |____postcss.config.js // postcss配置
    |____src // 組件源碼
    |____.npmignore // 指定發佈 npm 的時候須要忽略的文件和文件夾
    複製代碼
  1. mkdir learnnpm & cd learnnpm & npm init,根據提示依次填入信息,以後即生成 package.json
  2. 依次安裝依賴
    1. 由於使用webpack進行打包,安裝webpack相關依賴
       主依賴: yarn add webpack webpack-cli webpack-dev-server webpack-merge -D
       相關插件:clean-webpack-plugin html-webpack-plugin mini-css-extract-plugin
    
    2. 安裝react相關
       yarn add react react-dom 
    
    3. 安裝babel相關
       yarn add @babel/cli @babel/core @babel/preset-env @babel/preset-react babel-loader @babel/plugin-proposal-class-properties @babel/plugin-proposal-object-rest-spread -D 4. 安裝 typescript ts-loader fork-ts-checker-webpack-plugin 5. 安裝css相關 style-loader css-loader postcss-loader less less-loader url-loader file-loader autoprefixer 複製代碼
    • 完成以上步驟 package.json .babelrc webpack.config.js postcss.config.js相關內容以下
    {
        // ...
        "main": "lib/index.js", // 打包後的入口地址
        "scripts": {
            "start": "webpack-dev-server --config config/webpack.dev.config.js",
            "build": "webpack --config config/webpack.prod.config.js",
            "pub": "npm run build && npm publish" // 發佈 npm
        },
        // ...
        "dependencies": {
            "react": "^16.12.0",
            "react-dom": "^16.12.0"
        },
        "devDependencies": {
            "@babel/cli": "^7.8.4",
            "@babel/core": "^7.8.4",
            "@babel/preset-env": "^7.8.4",
            "@babel/preset-react": "^7.8.3",
            "@babel/plugin-proposal-class-properties": "^7.8.3",
            "@babel/plugin-proposal-object-rest-spread": "^7.8.3",
            "@types/react": "^16.9.19", // ts 須要用的相關庫types 文件
            "@types/react-dom": "^16.9.5",
            "@types/react-router-dom": "^5.1.3",
            "autoprefixer": "^9.7.4",
            "babel-loader": "^8.0.6",
            "clean-webpack-plugin": "^3.0.0",
            "css-loader": "^3.4.2",
            "fork-ts-checker-webpack-plugin": "^0.5.2", // ts類型校驗webpack插件
            "html-webpack-plugin": "^3.2.0",
            "less": "^3.11.1",
            "less-loader": "^5.0.0",
            "mini-css-extract-plugin": "^0.9.0", // 抽離css插件
            "postcss-loader": "^3.0.0",
            "style-loader": "^1.1.3",
            "ts-loader": "^6.2.1",
            "typescript": "^3.7.5",
            "url-loader": "^3.0.0",
            "webpack": "^4.41.6",
            "webpack-cli": "3.3.7",
            "webpack-dev-server": "^3.10.3",
            "webpack-merge": "^4.2.2"
        },
        "browserslist": [ // postcss autoprefixer 用到的配置
            "iOS >= 6",
            "Android >= 4",
            "IE >= 9"
        ]
    }
    複製代碼
    • .babelrc
    {
        "presets": [
            "@babel/preset-env",
            "@babel/preset-react",
        ],
        "plugins": [
            "@babel/plugin-proposal-class-properties",
            "@babel/plugin-proposal-object-rest-spread"
        ]
    }
    複製代碼
    • .postcss.config.js
    // postcss 配置參考 https://segmentfault.com/a/1190000008030425
    module.exports = {
        plugins: [
            require('autoprefixer')({ /* ...options */ })
        ]
    }
    複製代碼
    • webpack.base.js
    const path = require('path');
    
    module.exports = {
        module: {
            rules: [
                {
                    test: /\.(js|jsx)$/,
                    use: "babel-loader",
                    exclude: /node_modules/
                },
                {
                    test: /\.(ts|tsx)$/,
                    use: [
                        "babel-loader", 
                        {
                            loader: 'ts-loader', 
                            options: {
                                // 關閉類型檢查,即只進行轉譯, 類型檢查交給 fork-ts-checker-webpack-plugin 在別的的線程中作
                                transpileOnly: true
                            }
                        }
                    ],
                    exclude: /node_modules/
                },
                {
                    // .css/less 解析
                    test: /\.(less|css)$/,
                    use: [
                        'style-loader',
                        "css-loader",
                        "postcss-loader",
                        "less-loader"
                    ],
                },
                {
                    // 圖片解析
                    test: /\.(png|jpg|gif)$/,
                    include: path.resolve(__dirname, "..", "src"),
                    use: ["url-loader?limit=8192&name=assets/image/[name].[hash:4].[ext]"]
                },
                {
                    // 文件、字體解析
                    test: /\.(eot|woff|svg|ttf|woff2|otf|appcache|mp3|mp4|pdf)(\?|$)/,
                    include: path.resolve(__dirname, "..", "src"),
                    use: ["file-loader?name=assets/font/[name].[hash:4].[ext]"]
                },
            ]
        },
        resolve: {
            //後綴名自動補全,引入時可沒必要寫後綴名
            extensions: [".ts", ".tsx", ".js", ".jsx", ".less", ".css"]
        }
    };
    複製代碼
    • webpack.dev.config.js
    const path = require('path');
    const merge = require('webpack-merge');
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    const baseConfig = require('./webpack.base.js');
    
    const devConfig = {
        mode: 'development', // 開發模式
        entry: path.join(__dirname, "../example/src/app.js"), // 項目入口,處理資源文件的依賴關係
        output: {
            path: path.join(__dirname, "../example/src/"),
            filename: "bundle.js", 
            // 使用webpack-dev-sevrer啓動開發服務時,並不會實際在`src`目錄下生成bundle.js,打包好的文件是在內存中的,但並不影響咱們使用。
        },
        module: {
            rules: []
        },
        plugins: [
            new HtmlWebpackPlugin({
                title: 'learn npm',
                filename: "index.html",
                template: "./public/index.html",
                inject: true,
            }),
        ],
        devServer: {
            contentBase: path.join(__dirname, '../example/src/'),
            compress: true,
            port: 3001, // 啓動端口爲 3001 的服務
            // open: true // 自動打開瀏覽器
        },
    };
    module.exports = merge(devConfig, baseConfig);
    複製代碼
    • webpack.prod.config.js
    const path = require('path');
    const merge = require('webpack-merge');
    const baseConfig = require('./webpack.base.js');
    // const MiniCssExtractPlugin = require("mini-css-extract-plugin"); // 用於將組件的css打包成單獨的文件輸出到`lib`目錄中
    const { CleanWebpackPlugin } = require('clean-webpack-plugin');
    
    const prodConfig = {
        mode: 'production',
        entry: path.join(__dirname, "../src/index.tsx"),
        output: {
            path: path.join(__dirname, "../lib/"),
            filename: "index.js",
            libraryTarget: 'umd', // 採用通用模塊定義
            libraryExport: 'default', // 兼容 ES6 的模塊系統、CommonJS 和 AMD 模塊規範
        },
        module: {
            rules: [
                // 我在打包的沒有作css抽離,故註釋了
                // {
                // test: /\.css$/,
                // loader: [MiniCssExtractPlugin.loader, 'css-loader?modules'],
                // },
            ]
        },
        plugins: [
            // new MiniCssExtractPlugin({
            // filename: "main.min.css" // 提取後的css的文件名
            // }),
            new CleanWebpackPlugin(),
        ],
        externals: { // 定義外部依賴,避免把react和react-dom打包進去
            react: {
                root: "React",
                commonjs2: "react",
                commonjs: "react",
                amd: "react",
            },
            "react-dom": {
                root: "ReactDOM",
                commonjs2: "react-dom",
                commonjs: "react-dom",
                amd: "react-dom",
            }
        },
    };
    module.exports = merge(prodConfig, baseConfig); 
    複製代碼
    {
        "compilerOptions": {
            "target": "es6",
            "experimentalDecorators": true,
            "strictNullChecks": false,
            "module": "ESNext",
            "moduleResolution": "node",
            "jsx": "react",
            "noUnusedParameters": false,
            "noUnusedLocals": false,
            "esModuleInterop": true,
            "allowSyntheticDefaultImports": true,
            "skipLibCheck": true,
            "noImplicitAny": false,
            "noImplicitReturns": false,
            "noFallthroughCasesInSwitch": false,
            "alwaysStrict": false,
            "strict": false,
            "strictBindCallApply": false,
            "strictPropertyInitialization": false,
            "types": [
                "react",
                "react-dom",
                "node"
            ],
            // "baseUrl": "src",
            // 此處至關於webpack alias
            // "paths": {
            // "src/*": [
            // "*"
            // ]
            // }
        },
        "include": [
            "src/"
        ],
        "exclude": [
            "node_modules",
            "dist"
        ],
        "compileOnSave": false
    }
    複製代碼

ts 和 babel

上述配置使用的 babel ts 的工做方式爲 tsx -(ts-loader) -> es6 -(babel-loader) -> es5 即本項目的 ts-loader 分支html

  • 我這個項目的 master分支並無使用上述文章介紹的幾種方式,我是直接使用 tsc 編譯器(利用tsconfig.json配置),將源代碼tsx直接編譯成es5,即只用 ts-loader 處理 ts、tsx,tsconfig.json 的target設置成es5,引入以後運行良好,暫未發現異常

ts 與 babel幾種協同工做方式

  1. ts-loader + babel-loader
`ts-loader + tsc + tsconfig.json` 將 tsx 處理爲 es6

`babel-loader + babelrc` 接盤將 es6 按照 `@babel/presents-env` 處理爲 es5代碼

話外音: `ts-loader``new ForkTsCheckerWebpackPlugin` 配合 ==> webpack4以後`happypack`做用也小了,故不用了

如上文所配置
複製代碼
  1. babel7 + @babel/preset-typescript
引入 @babel/preset-typescript,來處理 tsx 類型信息(其做用就是刪除ts類型信息)

webpack 配置 js、jsx、ts、tsx 都交由babel-loader 處理

另外在啓動一個 tsc 服務檢查代碼類型 tsc --watch (package.json npm 腳本·

複製代碼
上述無論每種方法最終的結果都是隻轉換高版本ES的語法或者將TypeScript轉換爲ES5語法,但並不轉換api
  • 語法:let、const、class、Decorator
  • api:includes、Object.assign、Array.from、Promise、async await
  • 語法靠@babel/preset-env的相關配置進行轉義
  • api靠 @babel/polyfill@babel/runtime@babel/plugin-transform-runtime


  • 有個疑問我如今也沒有明確答案?
    像咱們寫的這些 npm包或ui組件庫,需不須要本身作 polyfill?
    仍是交給使用方即宿主環境作
    
    我看了 `antd-mobile` 打包後的文件,發現像 `Promise, Object.assign`並無作polyfill
    複製代碼

babel相關-@babel/polyfill、@babel/preset-env、@babel/plugin-transform-runtime

  • 參考文章
  1. Babel學習系列1-Babel歷史
  2. Babel學習系列2-Babel設計,組成
  3. Babel學習系列3-babel-preset,babel-plugin
  4. Babel學習系列4-polyfill和runtime差異(必看)
  5. Babel 編譯出來仍是 ES 6?難道只能上 polyfill?
  6. 這個網站,可讓你中止「瞎配」前端工具鏈
  7. www.tangshuang.net/7427.html
  • 動態polyfill方案主要是依據 @babel/preset-envuseBuiltIn肯定的
  • @babel/babel-polyfill 整個應用全局引入,模擬完整的ES6+環境
  • @babel/babel-runtime @babel/babel-plugin-transform-runtime 開發像vue這樣的框架、庫,提供一個沙盒環境不會污染原型鏈,後者主要爲前者提供引用幫助,減小代碼體積
相關文章
相關標籤/搜索