Ws.
文章
关于
返回
从零搭建 Webpack/React/Tailwindcss 项目
2023-01-15
编程

随着前端框架的发展,vue-clicreate-react-app 可以一键生成基础项目模板。但是对于新手来讲,跳过了很多细节。现在让我们从一个空文件夹开始,一步一步搭建起使用 Webpack 、React 和 Tailwindcss 框架的基础项目。在这个过程中,说明每个工具的作用,加深对现代前端工具链的理解。

一、准备

  • 具备 JS、CSS 基础
  • 了解打包概念
  • npm 或者 yarn 包管理工具,这里使用的是 yarn

二、使用 Webpack

webpack 可以对 js 、css 等资源文件进行打包,先不考虑打包复杂资源和框架,我们从最基础的 js和 css 开始打包。

2.1 开始一个新项目

  • 首先创建一个空文件夹,使用 yarn init 来初始化一个 package.json , 使用 git init 初始化 git

  • 创建 src 文件夹,然后在此文件夹加入 index.html index.jsindex.css

index.js

document.addEventListener('DOMContentLoaded', () => {
    document.getElementById('content').innerText = 'Content from JS';
})

index.css

body {
  text-align: center;
}

.content {
    color: blueviolet;
}

html

<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>React with Tailwindcss</title>
  	<link rel="stylesheet" href="./index.css">
  	<script src="./index.js"></script>
</head>
<body>
  <h1>HELLO WORLD!</h1>
  <div id="content" class="content"></div>
</body>
</html>

浏览器打开 index.html,最基础的模板就已经完成。 下一步使用 Webpack 打包 index.js 和 index.css。

2.2 Webpack 打包 JS

安装 webpack,为了可以使用webpack命令行,需要同时安装 webpack-cli

yarn add webpack webpack-cli -D

TIP: 调整 package.json 文件,确保我们安装包是私有的(private),并且移除 main 入口。这可以防止意外发布你的代码。

先试一下零配置运行 webpack. 零配置情况下,webpack会以./src/index.jsentry

执行 ./node-modules/.bin/webpack,执行成功后,会生成一个 dist 文件夹,文件夹中有 main.js ,这个是 webpack 默认的 output。现在修改 index.html

// 改为
<script src="../dist/index.js"></script>

刷新页面就可以看到效果和原来的一样。 我们就已经成功使用 webpack 打包 js 了。

package.json 中加入scripts, 后面可以使用 yarn build 来替代./node-modules/.bin/webpack

package.json

"version": "1.0.0",
...
"scripts": {
	"build": "webpack"
}
...

为了方便更高级的配置,在文件夹根目录建立 webpack.config.js output 里的 path 必须是绝对路径,所以需要用到 path 库。

const path = require('path');
module.exports = {
    mode: 'development',
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'main.js'
    },
}

2.3 Webpack 打包 CSS

打包 css 需要用到 css-loaderstyle-loader 来处理css文件。 先装上,再介绍它们的作用。

yarn add css-loader style-loader -D 

css-loader 用来处理css文件,webpack 本身只支持js打包,其他的文件需要用 loader 来完成。用了css-loader 就可以正常的把 css 打包进js文件中。对的 css-loader 只负责识别 css 并打包进 js,当然在 js 中的 css 是无法生效的。这时候 style-loader 就派上用场了,它会将 js中的 css 以内联样式,即在 header 中插入 <style>的方式注入到 html,这样css就生效了。修改 webpack.config.js 如下:

const path = require('path');
module.exports = {
    mode: 'development',
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'main.js'
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    'style-loader',
                    'css-loader'
                ]
            }
        ]
    }
}

这里需要注意,style-loadercss-loader 顺序不能错, loader 传递顺序是从后向前。意思是代码首先通过 css-loader, 然后再通过 style-loader。 关于 loader 的使用方法,请查看webpack 官方文档。

执行 yarn build 后, 修改 index.html<link rel="stylesheet" href="./index.css"> 去掉,因为 css 已经在 js中,不需要再加载。 后面会讲到如何把css进行分离和压缩。

三、引入 Tailwindcss

在引入 tailwindcss 之前,首先要介绍一下 PostCSSPostCSS 并不是一个对 css 进行压缩或者添加前缀的工具。 PostCSS 只做一个特别简单的事情,就是把 css 解析成语法树(Abstract Syntax Tree,AST),并提供操纵语法树的接口,随后在将语法树输出成字符串。这样一来就可以方便的对css进行修改,比如添加前缀 autoprefix 等等。 添加前缀等功能是 PostCSS 的一个插件。所以当我们说到 PostCSS,更多的是讲到 PostCSS 一个插件系统。 tailwindcss 同样提供PostCSS的插件,这样一来就可以使用 tailwindcss 自带的语法@tailwindcss @apply等。postcss-loaderPostCSS在 webpack 上的一个插件,用来webpack调用PostCSS

首先安装 tailwindcsspostcss-loader

yarn add tailwindcss postcss-loader -D

然后修改index.css, 引入tailwindcss组件

@tailwind base;

@tailwind components;

@tailwind utilities;

这里 @tailwindtailwindcss的语法糖, 不是 css 语法,所以 css-loader并不能直接解析它。正确流程是通过 PostCSStailwindcss 插件处理好以后,再传给 css-loader,所以 loader配置如下

...
module: {
    rules: [
        {
            test: /\.css$/,
            use: [
                'style-loader',
                'css-loader',
                {
                	loader: 'postcss-loader',
                	options: {
                		ident: 'postcss',
                		plugins: [
                			require('tailwindcss')
                		]
                	}
                }
            ]
        }
    ]
}
...

TIP: webpack requires an identifier (ident) in options when {Function}/require is used (Complex Options). The ident can be freely named as long as it is unique. It's recommended to name it (ident: 'postcss')

复制 tailwindcss 官网示例html代码到 index.htmlyarn build 以后刷新页面查看效果, 如果显示正常说明已经成功的引入 tailwindcss

四、优化 CSS

4.1将css独立成文件

目前为止,所有的 css 还在 js 中,如果我们想把 css 独立出来,不再通过 style-loader 插入到 header 中,那么将会用到webpack 插件mini-css-extract-plugin

yarn add mini-css-extract-plugin -D

用了这个插件以后,就不用在用 style-loader 了。

修改 webpack.config.js

const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
    mode: 'development',
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'main.js'
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: 'css/[name].[hash].css',
        })
    ],
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    'css-loader',
                    {
                        loader: 'postcss-loader',
                        options: {
                            ident: 'postcss',
                            plugins: [
                                require('tailwindcss')
                            ]
                        }
                    }
                ]
            }
        ]
    }
}

这样就会将css提取到 dist/css/文件夹下面。更多详细配置请参考官方文档。

4.2 自动插入 HTML

在上一步的过程中,我们生成的 css 文件名称你会发现后面多了一个随机的 hash值。 这个是为了避免缓存。每次生成的 hash值将会不一样,那么我们再引入html的时候就需要不断的修改。为了解决这个问题,引入插件 html-webpack-plugin。这个插件可以把编译好的资源自动插入到html中。

yarn add html-webpack-plugin@next -D

webpack.config.js 中配置

const HtmlWebpackPlugin = require('html-webpack-plugin');
   
...
plugins: [
   ...,
   new HtmlWebpackPlugin({
     template: './index.html',
  }),
]
...

4.3 压缩 CSS 文件

Tailwindcss 由于包含很多工具,所以 css 体积会比较大。打包以后 main.css 有 700+KB, 这里引入 PurgeCSS 进行压缩

yarn add @fullhuman/postcss-purgecss -D

通过名称可以看出,他是一个 PostCSS 插件。随着 PostCSS插件变多,我们将 PostCSS 的设置独立出来,新建 postcss.config.js

const PurgeCss = require('@fullhuman/postcss-purgecss');
const PurgeOptions = {

    // Specify the paths to all of the template files in your project 
    content: [
      './src/**/*.html',
      './src/**/*.vue',
      './src/**/*.jsx',
      // etc.
    ],
  
    // Include any special characters you're using in this regular expression
    defaultExtractor: content => content.match(/[\w-/:]+(?<!:)/g) || []
};

module.exports = {
    plugins: [
        require('tailwindcss'),
        PurgeCss(PurgeOptions),
    ],
}

webpack.config.js 中,把 loaders 改为

...
module: {
    rules: [
        {
            test: /\.css$/,
            use: [
                MiniCssExtractPlugin.loader,
                'css-loader',
                'postcss-loader',
            ]
        }
    ]
}
...

再次打包,就会发现 main.css 小了很多。至此 tailwindcss 打包完成。

五、引入 React

在使用 React 之前需要先安装一个编译器 Babel。它能让你编写的新版本 JavaScript 代码,在旧版浏览器中依然能够工作。

yarn add @babel/core @babel/cli @babel/preset-env @babel/preset-react -D

这里面 @babel/core 是核心包,用它来对我们的代码进行编译转换。 @babel/cli可以让你在命令行中使用 babel 命令。 preset 是转换特定代码的预设。 @babel/preset-envES6+ 代码转换成普通 js, @babel/preset-react 类似,不过是专门针对 React 的,将转换 jsx 代码为普通 js。装完 babel 以后,在根目录创建 babel配置文件- .babelrc, 然后将我们安装的两个 preset 设置好。

{
  "presets": ["@babel/env", "@babel/preset-react"]
}

使用 Webpack 打包 React 相关代码时,需要用 babel 进行编译转换,所以需要安装 babel-loader 配合webpack 使用。

yarn add babel-loader -D

修改 webpack.config.js

...
modules: {
	rules: [
    {
      test: /\.(js|jsx)$/,
      exclude: /(node_modules|bower_components)/,
      loader: "babel-loader",
    },
  ],
},
resolve: [extensions: ["*", ".js", ".jsx"]],
...

这里意思为使用 babel-loader 来处理除了 node_modulesbower_component文件夹外的 js 或者 jsx 文件。 resolve 配置可以在 import 文件时,不用写上后缀。 比如 import 'index.js' 可以写为 import 'index'

接下来安装 reactreact-dom

yarn add react react-dom

然后修改 index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/app';

import './index.css';

ReactDOM.render(<App />, document.getElementById("content"));

新建 /src/components/app.jsx

import React, { Component} from "react";

class App extends Component{
  render(){
    return(
      <div className="App">
        <div class="max-w-sm mx-auto flex p-6 bg-white rounded-lg shadow-xl">
            <div class="flex-shrink-0">
                <img class="h-12 w-12" src="/img/logo.svg" alt="ChitChat Logo" />
            </div>
            <div class="ml-6 pt-1">
                <h4 class="text-xl text-gray-900 leading-tight">ChitChat</h4>
                <p class="text-base text-gray-600 leading-normal">You have a new message!</p>
            </div>
        </div>
      </div>
    );
  }
}

export default App;

yarn build 后打开页面,能够正常看到 app.jsx 内容说明 react 已经生效。

六、模块热替换 HMR

在开发页面时,每修改一下就需要build然后刷新页面严重影响了开发效率。webpack 模块热替换可以自动局部刷新更新的内容。

Tip: 不要在生产环境中使用 HMR

要启用 HMR很简单,安装好 webpack-dev-serve 并对其进行配置即可。

yarn add webpack-dev-server -D

安装好以后在 package.json 中加入 scripts 方便我们调用

...
"scripts": {
	...,
	"dev": "webpack-dev-server"
},
...

接着修改 webpack.config.js 添加 webpack-dev 配置

const webpack = require('webpack');
...
plugins: [
  ...,
  new webpack.HotModuleReplacementPlugin()
],
devServer: {
  contentBase: path.join(__dirname, "dist"),
  port: 3000,
  hotOnly: true,
},
...

这个时候如果你运行 yarn dev ,打开页面并进行一些修改,你会发现并没有自动刷新。 因为现在只是 webpack 做好了热更新准备,我们还需要告诉 webpack 我们更新了哪些内容。所以还需要安装一个 react-hot-loader 组件

yarn add react-hot-loader -D

安装好以后修改 app.jsx

import {hot} from "react-hot-loader";

import React, { Component} from "react";

class App extends Component{
  render(){
    return(
      <div className="App">
        <div className="max-w-sm mx-auto flex p-6 bg-white rounded-lg shadow-xl">
            <div className="flex-shrink-0">
                <img className="h-12 w-12" src="/img/logo.svg" alt="ChitChat Logo" />
            </div>
            <div className="ml-6 pt-1">
                <h4 className="text-xl text-gray-900 leading-tight">NIHAO HMR</h4>
                <p className="text-base text-gray-600 leading-normal">You have a new message!</p>
            </div>
        </div>
      </div>
    );
  }
}

export default hot(module)(App);

此时修改 app.jsx 页面就会自动进行同步了。

但是...接下来会发现修改 CSS 并不会引发同步,没关系,MiniCssExtractPlugin 早已有此参数, 修改一下webpack.config.jsloader 配置即可

...
{
    loader: MiniCssExtractPlugin.loader,
        options: {
            // only enable hot in development
            hmr: true,
            // if hmr does not work, this is a forceful method.
            reloadAll: true,
        },
},
...

完结! 撒花!

示例代码在 : Github/wensonsmith/React with Tailwindcss from scratch

七、参考资料

  1. Create a react app from scratch
  2. Webpack Document
  3. It's time for everyone to learn about PostCSS
  4. Tailwindcss Document
Ws.
Keep looking don't settle
@2023 Wenson | Code the Ambition | V1.1.2