背景

想结合tailwindcss(CSS)和styled-components(CSS in JS)这两种React中常用的CSS框架,于是有了使用twin这个框架的想法,这篇文章就简单讲一下怎么初始化配置环境。另外会涉及到几个React项目中常见的问题以及解决方法。

快速开始

可以使用已经打包好的Starter:twin_webpack

从零开始

安装CRA和twin

Install Create React App

npm init create-react-app my-app

Install the dependencies

npm install twin.macro tailwindcss styled-components

初始化全局样式

创建文件src/styles/GlobalStyles.js,并填入以下内容:

// src/styles/GlobalStyles.js
import React from 'react'
import { createGlobalStyle } from 'styled-components'
import tw, { theme, GlobalStyles as BaseStyles } from 'twin.macro'

const CustomStyles = createGlobalStyle`
  body {
    -webkit-tap-highlight-color: ${theme`colors.purple.500`};
    ${tw`antialiased`}
  }
`

const GlobalStyles = () => (
  <>
    <BaseStyles />
    <CustomStyles />
  </>
)

export default GlobalStyles

然后在src/index.js中引入该全局样式:

// src/index.js
import React from 'react'
import GlobalStyles from './styles/GlobalStyles'
import App from './App'
import { createRoot } from 'react-dom/client'

const container = document.getElementById('root')
const root = createRoot(container)
root.render(
  <React.StrictMode>
    <GlobalStyles />
    <App />
  </React.StrictMode>,
)

配置twin

package.json中添加以下字段:

// package.json
"babelMacros": {
  "twin": {
    "preset": "styled-components"
  }
},

启用auto css prop feature(2023.4.7更新)

这里是重点,如果少了这一步,则不能在jsx组件中使用

tw="..."和css={[tw`...`, tw`...`]}

这样的特性!!!这个很重要!

安装babel-plugin-styled-components:

npm i -D babel-plugin-styled-components

在项目根目录下创建.babelrc文件,并添加以下代码:

{
  "presets": ["@babel/preset-react"],
  "plugins": [
    "macros",
    "babel-plugin-styled-components"
  ]
}

到此已经可以开始使用twin.macro的特性了。具体用法可以看一下官方仓库

配置webpack

如果想用webpack作为打包工具的话,请继续往下看。

安装webpack(我用的版本是5.78.0):

npm install webpack webpack-cli --save-dev
npm install babel-loader @babel/core @babel/preset-env --save-dev
npm install css-loader style-loader --save-dev

创建 Webpack 配置文件。

// 在项目根目录下创建webpack.config.js
// 需要注意的是要把twin用到的插件添加进去
// 另外要配置一下webpack-dev-server
// 此外我还配置了alias和静态资源打包
// 下面是我初始化好的配置

const path = require('path');
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
    // 入口文件
    entry: './src/index.js',
    // 出口文件
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'dist'),
    },
    module: {
        rules: [
            {
                test: /\.(js|jsx)$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: [
                            '@babel/preset-env',
                        ],
                        plugins: [
                            "babel-plugin-macros", // twin.macro使用
                            // 其他插件
                        ],
                        babelrc: true,
                    },
                },
            },
            {
                test: /\.css$/,
                use: ['style-loader', 'css-loader'],
            },
            {
                test: /\.(png|svg|jpg|jpeg|gif)$/i,
                use: [
                    {
                        loader: 'url-loader', // file-loader
                        options: {
                            limit: 8192, // 图片大小限制,超过此大小将被打包成文件
                            name: '[name].[ext]', // 重命名输出的文件名
                            outputPath: 'images/', // 输出路径
                        },
                    },
                ],
            },
        ],
    },
    resolve: {
        extensions: ['.js', '.jsx'],
        alias: {
            "@": path.resolve('src'),
            "images": path.resolve('src/assets/images'),
        },
    },

    // 用于指定 webpack-dev-server 的配置
    devServer: {
        static: path.resolve(__dirname, 'public'),
        compress: true,
        port: 3000,
        open: true,
        hot: true,
        historyApiFallback:true  //缺少该配置,会出现Cannot Get的错误
    },
    plugins:[
        new HtmlWebpackPlugin({
            template:path.resolve(__dirname,'public/index.html')
        })
    ]
};

修改项目的 package.json 文件,添加打包脚本。

// 我是在原有的基础上添加了最下面的两个脚本,自己实时调试的话用webpack:dev

  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "webpack:build": "webpack",
    "webpack:dev": "webpack-dev-server --mode development"
  },

到此结束。

alias配置与静态资源打包

alias配置

上面的webpack.config.js中已经配置好了,当时出了些奇怪的bug,像是由缓存引起的,让我白白调试半天……(~心态爆炸~)

resolve: {
        extensions: ['.js', '.jsx'],
        alias: {
            "@": path.resolve('src'),
            "images": path.resolve('src/assets/images'),
        },
    },

静态资源打包

安装file-loaderurl-loader(依赖于file-loader):

npm install file-loader --save-dev
npm install url-loader --save-dev

添加配置:

module: {
            {
                test: /\.(png|svg|jpg|jpeg|gif)$/i,
                use: [
                    {
                        loader: 'url-loader', // file-loader
                        options: {
                            limit: 8192, // 图片大小限制,超过此大小将被打包成文件
                            name: '[name].[ext]', // 重命名输出的文件名
                            outputPath: 'images/', // 输出路径
                        },
                    },
                ],
            },
        ],
    },

创建src/assets/images目录,并把用到的图片类静态资源放在这里。

之后在CSS中就可以用url(images/XXX.XX)直接使用图片等静态资源了。(可以根据需要换为其他路径)

其实这里能用这个路径使用图片等静态资源是因为:上面url-loader这个插件的outputPath我们设置的是images/,所以webpack会把打包后的图片放在服务器的/images/XXX这个位置。如下图所示:%E6%88%AA%E5%B1%8F2023-04-06%2021.00.31.png

此外就是我们设置了 "images": path.resolve('src/assets/images') 的alias,恰巧在本地索引的话也是用"images/XXX"这个路径,所以IDE这边不会显示这个URL是无效链接。你在css中的url()中填的是什么路径,浏览器就会在该路径下去请求资源。需要注意的是在以上的配置环境下,css中不能用alias。就是你字面上写的是什么URL,到时候请求的就是服务器根路径/URL这个URL。所以我让url-loader把打包后的资源放在和这个“同名”的images/路径下。

但是我这里还是有点小问题,就是只在CSS中使用某个静态资源的时候,webpack不能发现并打包,需要手动import以下这个资源才行。使用的时候需要注意了。

2023.4.11补充

用Webpack打包的话,/dist/index.html中的图标静态资源的路径需要改一下。举个例子:

假如我项目的首页路径是https://qiuyedx.github.io/NavPage,静态资源被打包到/images路径下,那么路径应改为/NavPage/images/XXX.ico

拓展资料


A Student on the way to full stack of Web3.