从0开始搭建preact开发环境
动机
react
是个优秀的库,但是如果用到移动端未免有点儿大材小用。于是我在开发个人项目的时候,便选择了preact
。
值得一提的是,preact
实现了很多的react
功能,但是呢,preact
也牺牲了react
的部分功能。毕竟preact
才2KB大小。
不同
preact
在api上做了很多开发上的便捷处理,比如在render
方法里传入了props
和state
。为了精简,preact
舍弃了部分功能,主要是以下几个功能:
1. `PropsType`验证(用不怎么到的功能)
2. `props.children`总是一个数组(差别不怎么大)
3. `Synthetic Events`(react庞大的原因之一,很大一部分代码用于事件实现)
4. `render`方法,`preact`接受第三个参数(不怎么用得到)
复制代码
妥协处理
preact-compat
提供了完整的react
API和功能支持。
准备工作
我的文件目录如下
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:
- less-loader(需要
less
) - css-loader
- style-loader(用于开发环境)
- ts-loader(需要
typescript
) - url-loader(字体,图片之类的处理)
- babel-loader(需要
babel-core
)
安装以上几个loader
以及对应的需要的包。
首先安装webpack
,我选择使用4.X的版本。 接着安装babel-core
,webpack-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-loader
的modules
参数设置为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才能游刃有余。
配置弄完又是一个下午没有了。。。