從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才能遊刃有餘。
配置弄完又是一個下午沒有了。。。