Migrating Baidu Tieba Frontend to React: From fis3 to Modern Build Tools
This article details the challenges of evolving a large legacy frontend codebase, the transition from jQuery and fis3 to React with TypeScript, the integration of webpack-like features, server‑side rendering techniques, and the use of yog2, Fit, and related tooling to streamline modern web development.
Frontend development is a demanding field with constantly changing frameworks, scaffolding, and toolchains, and large legacy projects often struggle to keep up.
In Baidu Tieba, a decade‑old codebase accumulated massive, intertwined frontend code built around jQuery and PHP, making incremental changes difficult due to compilation scripts, module coupling, and a full PHP environment.
fis3 vs Webpack
fis3 is a complete frontend build tool, while webpack focuses on module bundling. fis3 now supports npm packaging similar to webpack.
// Disable default fis‑components
fis.unhook('components')
// Use node_modules hook
fis.hook('node_modules', {ignoreDevDependencies: true})Project files can be split into client and server for on‑demand loading:
fis.set('project.files', [
'/client/index.html',
'/server/**'
])TypeScript compilation for client files:
fis.match('/client/**.{jsx,tsx}', {
rExt: 'js',
parser: fis.plugin('typescript', {module: 1, target: 0})
})Production settings for CDN domain, compression, hashing, and minification:
const production = fis.media('production')
production.match('*.{css,less,scss,sass,js}', {domain: 'http://cdn.example.com'})
production.match('*.css', {optimizer: fis.plugin('clean-css')})
production.match('*.{ts,tsx,js,jsx,css,scss,png,jpg,jpeg,gif,bmp,eot,svg,ttf,woff,woff2}', {useHash: true})
production.match('*.png', {optimizer: fis.plugin('png-compressor')})
production.match('*.{js,tsx}', {optimizer: fis.plugin('uglify-js')})Package all JS and CSS into bundles:
const pack = {
'/client/pkg/bundle.js': ['/client/index.tsx', '/client/index.tsx:deps'],
'/client/pkg/bundle.css': ['*.scss', '*.css']
}
production.match('::package', {packager: fis.plugin('deps-pack', pack)})yog2 vs Express
yog2 is a Node.js UI layer built on Express, offering app splitting for collaborative development and HTTP‑push deployment via fis3.
npm install yog2 -g yog2 run -e dev production.match('*', {
charset: 'utf8',
deploy: [fis.plugin('http-push', {receiver: 'http://127.0.0.1:8080/yog/upload', to: '/'})]
})yog2 supports bigpipe, quickling, server rendering, MVC routing, and hot updates with the --watch flag.
Fit vs Antd
Fit is an internal React component library written in TypeScript, maintained by the FEX team, and includes an isomorphic Redux toolset for server‑side rendering.
React Server‑Side Rendering Enterprise Practice
Backend rendering preparation involves providing a base HTML template and API endpoints, then using fit‑isomorphic‑redux‑tools ’s serverRender function.
Backend Template Preparation
The server template supplies a basic HTML file and APIs. The serverRender function injects rendered HTML and initial state into the template.
// server/action/index.ts
import * as React from 'react'
import routes from '../../client/routes'
import {basename} from '../../client/config'
import rootReducer from '../../client/reducer'
import serverRender from 'fit-isomorphic-redux-tools/lib/server-render'
import * as fs from 'fs'
import * as path from 'path'
const htmlText = fs.readFileSync(path.join(__dirname, '../../client/index.html'), 'utf-8')
export default async (req, res) => {
serverRender({req, res, routes, basename, rootReducer, htmlText, enableServerRender: true})
}Router configuration forwards non‑API requests to the server‑render action.
// server/router.ts
import initService from './service'
export default router => {
router.use((req, res, next) => {
/^\/api\//.test(req.path) ? next() : router.action('index')(req, res, next)
})
initService(router)
}Service layer defines API methods with decorators for routing.
// server/service/index.ts
import {initService, routerDecorator} from 'fit-isomorphic-redux-tools/lib/service'
export default initService
class Service {
@routerDecorator('/api/simple-get-function', 'get')
simpleGet(options) { return `got get: ${options.name}` }
@routerDecorator('/api/simple-post-function', 'post')
simplePost(options) { return `got post: ${options.name}` }
@routerDecorator('/api/current-user', 'get')
async currentUser(options, req) {
return await new Promise(resolve => setTimeout(() => resolve('my name is huangziyi'), 1000))
}
}
new Service()Frontend template ( client/index.html) includes a placeholder for the initial state and script references.
<!DOCTYPE html>
<html>
<body>
<div id="react-dom"></div>
</body>
<script>window.__INITIAL_STATE__ = __serverData('__INITIAL_STATE__');</script>
<script src="./static/mod.js"></script>
<script src="./index.tsx"></script>
</html>The entry file creates a router with routes, basename, and Redux reducer, then renders it.
// client/index.tsx
import * as React from 'react'
import * as ReactDOM from 'react-dom'
import routerFactory from 'fit-isomorphic-redux-tools/lib/router'
import routes from './routes'
import {basename} from './config'
import reducer from './reducer'
import './index.scss'
const router = routerFactory(routes, basename, reducer)
ReactDOM.render(router, document.getElementById('react-dom'))Routes are defined with react‑router, and reducers are combined with redux and react‑router‑redux.
// client/routes.tsx
import * as React from 'react'
import {Route, IndexRoute} from 'react-router'
import Layout from './routes/layout/index'
import Home from './routes/home/index'
import PageA from './routes/page-a/index'
import PageB from './routes/page-b/index'
export default (
<Route path="/" component={Layout}>
<IndexRoute component={Home}/>
<Route path="page-a" component={PageA}/>
<Route path="page-b" component={PageB}/>
</Route>
) // client/reducer.tsx
import {combineReducers} from 'redux'
import {routerReducer} from 'react-router-redux'
import layout from './routes/layout/reducer'
export default combineReducers({routing: routerReducer, layout})Actions use the provided fetch helper, which works both on the client and server.
// client/stores/user/action.tsx
import fetch from 'fit-isomorphic-redux-tools/lib/fetch'
export const SIMPLE_GET_FUNCTION = 'SIMPLE_GET_FUNCTION'
export const simpleGet = () => fetch({type: SIMPLE_GET_FUNCTION, url: '/api/simple-get-function', params: {name: 'huangziyi'}, method: 'get'})Core Middleware and Service Functions
Promise middleware handles async actions uniformly on front‑end and back‑end.
export default store => next => action => {
const {promise, type, ...rest} = action
if (!promise) return next(action)
const REQUEST = type + '_REQUEST'
const SUCCESS = type + '_SUCCESS'
const FAILURE = type + '_FAILURE'
if (process.browser) {
next({type: REQUEST, ...rest})
return promise.then(req => {next({data: req.data, type: SUCCESS, ...rest}); return true})
.catch(error => {next({error, type: FAILURE, ...rest}); console.log('FrontEnd PromiseMiddleware Error:', error); return false})
} else {
const result = promise(action.data, action.req)
if (typeof result.then === 'function') {
return result.then(data => {next({data, type: SUCCESS, ...rest}); return true})
.catch(error => {next({error, type: FAILURE, ...rest}); console.log('ServerEnd PromiseMiddleware Error:', error); return false})
} else {
return next({type: SUCCESS, data: result, ...rest})
}
}
}Service registration uses a map and decorator to bind URLs to functions.
const services = new Map()
export const routerDecorator = (url, method) => (target, key, descriptor) => {
services.set(url, {value: descriptor.value, method})
return descriptor
}
export const initService = router => {
for (let key of services.keys()) {
const target = services.get(key)
router[target.method](key, async (req, res) => {
const params = target.method === 'get' ? req.query : {...req.body, ...req.query}
const result = await target.value(params, req)
res.json(result)
})
}
}Conclusion
Adopting React and its ecosystem reduces maintenance costs and improves development efficiency by enforcing modularity and separating data from presentation with Redux. Server‑side rendering boosts first‑page performance, though it currently requires a double render to handle async data. Modern build tools like fis3 now support npm ecosystems, and alternatives such as yog2 provide a complete enterprise development workflow.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
21CTO
21CTO (21CTO.com) offers developers community, training, and services, making it your go‑to learning and service platform.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
