从0开始搭建preact开发环境

傅英喆
2023-12-01

从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才能游刃有余。

配置弄完又是一个下午没有了。。。

 类似资料: