從0開始搭建preact開發環境

DoubleDimos發表於2018-08-10

從0開始搭建preact開發環境

動機

react是個優秀的庫,但是如果用到移動端未免有點兒大材小用。於是我在開發個人專案的時候,便選擇了preact

值得一提的是,preact實現了很多的react功能,但是呢,preact也犧牲了react的部分功能。畢竟preact才2KB大小。

不同

preact在api上做了很多開發上的便捷處理,比如在render方法裡傳入了propsstate。為了精簡,preact捨棄了部分功能,主要是以下幾個功能:

1. `PropsType`驗證(用不怎麼到的功能)
2. `props.children`總是一個陣列(差別不怎麼大)
3. `Synthetic Events`(react龐大的原因之一,很大一部分程式碼用於事件實現)
4. `render`方法,`preact`接受第三個引數(不怎麼用得到)
複製程式碼

妥協處理

preact-compat提供了完整的reactAPI和功能支援。

準備工作

我的檔案目錄如下

src
    assets(存放字型、圖片等)
    less(存放less檔案)
    tsx(存放tsx檔案)
    index.html
    index.tsx
config
    webpack.config.js (開發用配置)
    webpack.build.config.js(生產用配置)

.babelrc (babel配置檔案)
tsconfig.json (typescript 配置檔案)

複製程式碼

由於是從零開始,因此首先需要確定的就是技術棧。根據以往的經驗,我選取的技術棧是 less + typescript。選擇使用webpack作為打包工具。那麼自然而然地想到以下幾個loader:

  1. less-loader(需要less
  2. css-loader
  3. style-loader(用於開發環境)
  4. ts-loader(需要typescript)
  5. url-loader(字型,圖片之類的處理)
  6. babel-loader(需要babel-core)

安裝以上幾個loader以及對應的需要的包。

首先安裝webpack,我選擇使用4.X的版本。 接著安裝babel-corewebpack-cli, webpack-dev-server,babel-preset-env

  yarn add webpack webpack-cli webpack-dev-server babel-preset-env --dev
複製程式碼

接著,我們安裝程式碼分離以及開發時需要的外掛。注意的是,webpack下,需要安裝extract-text-webpack-plugin@next,否則會出錯。

    yarn add clean-webpack-plugin html-webpack-plugin extract-text-webpack-plugin@next --dev
複製程式碼

webpack配置

接下來,我們開始配置webpack的配置檔案。我將配置檔案分為兩類,一個是生產用,一個是開發用。並將它們放到{base_path}/config下面。

  // webpack.config.js
const html = require('html-webpack-plugin');
const clean = require('clean-webpack-plugin');
const path = require('path');
const extract = require('extract-text-webpack-plugin');

module.exports = {
    entry: './src/index.tsx',
    module: {
        rules: [
            {
                test: /\.tsx?$/,
                use: ['babel-loader', 'ts-loader']
            },
            {
                test: /\.less$/,
                use: [
                    'style-loader', 
                    {
                        loader: 'css-loader',
                        options: {
                            modules: true
                        }
                    }, 
                    'less-loader'
                ]
            },
            {
                test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
                loader: 'url-loader',
                options: {
                    limit: 1000,
                    name: 'static/fonts/[name].[hash].[ext]'
                }
            },
            {
                test: /\.(png|svg|jpg|gif)$/i,
                loader: 'url-loader',
                options: {
                    name: 'static/img/[name].[hash].[ext]',
                    limit: 4096
                }
            }
        ]
    },
    mode: 'development',
    resolve: {
        extensions: ['.jsx', '.js', '.tsx', '.ts']
    },
    plugins: [
        new html({
            template: './src/index.html'
        }),
        new clean([path.resolve('./dist')], {
            root: path.resolve('./')
        })
    ],
    devtool: 'source-map',
    devServer: {
        contentBase: path.resolve("./dist")
    }
}
複製程式碼

需要注意的是,css modules開啟需要css-loadermodules引數設定為true

生產用配置檔案差別不大,主要還是把css檔案抽離出來以及進行了程式碼分離。webpack@4.x簡化了程式碼分離的操作,直接使用splitChunks就行了。

// webpack.build.config.js
const html = require('html-webpack-plugin');
const clean = require('clean-webpack-plugin');
const path = require('path');
const extract = require('extract-text-webpack-plugin');

module.exports = {
    entry: path.resolve('./src/index.tsx'),
    module: {
        rules: [
            {
                test: /\.tsx?$/,
                use: ['babel-loader', 'ts-loader']
            },
            {
                test: /\.less$/,
                use: extract.extract({
                    use: [
                        {
                            loader: 'css-loader',
                            options: {
                                sourceMap: true
                            }
                        },
                        {
                            loader: 'less-loader',
                            options: {
                                sourceMap: true
                            }
                        }
                    ]
                })
            },
            {
                test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
                loader: 'url-loader',
                options: {
                    limit: 1000,
                    name: 'static/fonts/[name].[hash].[ext]'
                }
            },
            {
                test: /\.(png|svg|jpg|gif)$/i,
                loader: 'url-loader',
                options: {
                    name: 'static/img/[name].[hash].[ext]',
                    limit: 4096
                }
            }
        ]
    },
    mode: 'production',
    resolve: {
        extensions: ['.jsx', '.js', '.tsx', '.ts']
    },
    optimization: {
        splitChunks: {
            cacheGroups: {
                vender: {
                    name: 'vendor',
                    minSize: 0,
                    chunks: 'initial',
                    test: /node_modules/,
                }
            }
        },
        runtimeChunk: {
            name: 'manifest'
        },
        minimize: true
    },
    output: {
        path: path.resolve('./dist'),
        filename: 'static/js/[name].[hash:8].js',
        chunkFilename: 'static/js/[name].[chunkhash:8].js',
        publicPath: './'
    },
    plugins: [
        new html({
            template: './src/index.html'
        }),
        new clean([path.resolve('./dist')], {
            root: path.resolve('./')
        }),
        new extract({
            filename: 'static/css/[name].[hash:8].css',
            allChunks: true
        })
    ]
}

複製程式碼

值得一提的是,我在處理路勁的時候都用到了path.resolve。此時,./目錄對應的並非config檔案所在目錄,而是專案根目錄。

接著我們增加兩條命令到package.json裡面:

  "scripts": {
    "serve": "webpack-dev-server --config ./config/webpack.config.js",
    "build": "webpack --config ./config/webpack.build.config.js"
  }
複製程式碼

完了嗎?當然沒有,還有最主要的一點,就是babel的配置問題。

babel配置

babel除了配置perset以外,還需要配置一個很重要的transform-react-jsx外掛。這個外掛目的是用來轉換jsx語法的。然而,它預設是將jsx語法轉換為React.createElement這個函式包裝的virtual dom。我們需要更改一下,按照傳統,我們使用preact.h替代。

// .babelrc
{
    "presets": [
        [
            "env",
            {
                "modules": false
            }
        ]
    ],
    "plugins": [
        [
            "transform-react-jsx",
            {
                "pragma": "preact.h"
            }
        ]
    ]
}
複製程式碼

typescript配置

這裡只有一個地方需要注意的,那就是jsx語法應該保留,不能轉換。根據以上的配置檔案,ts檔案首先傳遞給ts-loader,接著傳遞給babel-loader。我們把jsx語法交給babel轉換,因此需要保留。

{
    "compilerOptions": {
        "target": "es2016",
        "moduleResolution": "node",
        "jsx": "preserve",
        "sourceMap": true
    }
}
複製程式碼

結語

webpack的配置真的是一言難盡啊。。。另外開發過程中,也有需要注意的地方。css modules不要用import,應該用require。當然,你得自己定義一個require。另外,jsx語法的檔案,都必須匯入preact的包,並且還有講究。

import * as preact from 'preact'; // 必須匯入成 * as preact

declare function require(...args: any[]): any; // 定義require函式

const test = require('../less/test.less'); 

export default class App extends preact.Component<any, any> {
    constructor(props) {
        super(props);
    }
    
    render(props, state) {
        return (
            <div className={ test.test }>hello world!</div>
        )
    }
}

複製程式碼

基本上就是如此了,webpack的配置還是很麻煩。並沒有傳說中的0配置那麼神。需要自定義功能的時候,還是需要熟練掌握webpack才能遊刃有餘。

配置弄完又是一個下午沒有了。。。

相關文章