13.JS学习篇-ES6 React 项目模板

简介: 【8月更文挑战第25天】

1.项目能力支持
1.项目初始化脚手架
1.前端编码规范工程化(lint工具、Node CLI等)

2.用工具提升项目的编码规范,如:eslint、stylelint、commitlint、markdownlint、husky等

3.工具对于JavaScript、Typescript、React、Vue等不同类型的前端项目下的标准的语法限制;

2.相关基础功能
• React : 前端页面展示框架;
• Redux :状态管理;
• React Router :前端路由;
• Connected React Router :支持将 Redux 与 React Router 进行绑定;
• Express 服务端;
• TypeScript 类型检查;
• Webpack 打包构建工具;
• Babel : ES6+ 转 ES5 工具;
• nodemon :监测 Node 应用变化时,自动重启服务器;
• axios 基于 Promise 的 HTTP 客户端;
• react-helmet :在客户端及服务端上管理标题、 meta 、样式和脚本标签;
• loadable-component :支持组件的懒加载;
• Webpack Dev Middleware :通过 Express 服务器提供 webpack 服务;
• Webpack Hot Middleware :支持基于 Express 的热更新;
• Webpack Bundle Analyzer :打包分析工具;
• morgan :服务器日志;
• terser-webpack-plugin :压缩 JS ;
• css-minimizer-webpack-plugin :压缩 CSS ;
3.运行指令
使用cross-env提供跨平台的设置及环境变量:

step1

====================================安装初始脚手架

命令行 start

sudo npm install -g encode-fe-lint

sudo encode-fe-lint init

React 项目 (TypeScript)

Y Y Y

====================================命令行 end

step2

基础环境配置

  • 查看 package.json 文件

"private"

"script"-"preinstall"/"prepare"/"init"

"engines"

"devDependencies"

"dependencies"

====================================命令行 start

sudo npm install -g pnpm

sudo pnpm install

====================================命令行 end

2.项目初始化配置
项目目录:

  1. 新建 babel.config.js,以及内部配置

  2. tsconfig 的配置, tsconfig.json

sudo npm install -g typescript

sudo tsc --init

  1. 实现相关的 postcss,创建 postcss.config.js

  2. webpack cross-env

我们需要客户端和服务端渲染,所以如下

webpack 目录

  • base.config.ts

  • client.config.ts

  • server.config.ts

我们看到网上很多都是如下

  • webpack.common.js

  • webpack.dev.js

  • webpack.prod.js

webpack 是纯用于打包的,和 js/ts 没有关系的

webpack5 中 MiniCssExtractPlugin,将 css 分离出

progressPlugin 编译进度包

webpack-manifest-plugin ssr 中需要引入的,页面中的基本信息

loadablePlugin 分包的方式引入子包的内容

DefinePlugin 定义全局变量

bundle-analyze plugin 分析线上的包

⭐️⭐️⭐️⭐️⭐️⭐️⭐️

通用的能力做抽离,根据不同的环境,进行不同的配置。

先打包构建,生产产物

nodemon.json,开发过程中,可以监听 public 下面文件的变化&&服务端的更新

到这一步为止,基础环境已经配置完成.不用脚手架,自己手写也可以,加油吧~

3.客户端配置
1.入口文件
// src/app/index.tsx
const App = ({ route }: Route): JSX.Element => (




Logo


{config.APP.title}





{/ Child routes won't render without this /}
{renderRoutes(route.routes)}

};

// src/client/index.tsx
const render = (Routes: RouteConfig[]) =>
ReactDOM.hydrate(

{renderRoutes(Routes)}
,
document.getElementById('react-view'),
);

// loadable-component setup
loadableReady(() => render(routes as RouteConfig[]));

2.错误边界处理
import { ReactNode, PureComponent } from "react";

interface Props {
children?: ReactNode;
}
interface State {
error: Error | null;
errorInfo: { componentStack: string } | null;
}
class ErrorBoundary extends PureComponent {
constructor(props: Props) {
super(props);
this.state = { error: null, errorInfo: null };
}
componentDidCatch(error: Error, errorInfo: { componentStack: string }): void
// Catch errors in any components below and re-render with error message
this.setState({ error, errorInfo });
// You can also log error messages to an error reporting service here
}
render(): ReactNode {
const { children } = this.props;
const { errorInfo, error } = this.state;
// If there's an error, render error path
return errorInfo ? (


Something went wrong.



{error && error.toString()}


{errorInfo.componentStack}


) : (
children || null
);
}
}
export default ErrorBoundary;

3.页面入口配置
const Home: FC = (): JSX.Element => {
const dispatch = useDispatch();
const { readyStatus, items } = useSelector(
({ userList }: AppState) => userList,
shallowEqual
);
// Fetch client-side data here
useEffect(() => {
dispatch(fetchUserListIfNeed());
}, [dispatch]);
const renderList = () => {
if (!readyStatus || readyStatus === "invalid" || readyStatus === "request"
return

Loading...

;
if (readyStatus === "failure") return

Oops, Failed to load list!

;
return ;
};
return (


{renderList()}

);
};
// Fetch server-side data here
export const loadData = (): AppThunk[] => [
fetchUserListIfNeed(),
// More pre-fetched actions...
];
export default memo(Home);
1

4.路由配置
export default [
{
component: App,
routes: [
{
path: "/",
exact: true,
component: AsyncHome, // Add your page here
loadData: loadHomeData, // Add your pre-fetch method here
},
{
path: "/UserInfo/:id",
component: AsyncUserInfo,
loadData: loadUserInfoData,
},
{
component: NotFound,
},
],
},
] as RouteConfig[];

4.服务端配置
1.请求配置
// 使用https://jsonplaceholder.typicode.com提供的接口设置请求

export default {
HOST: 'localhost',
PORT: 3000,
API_URL: 'https://jsonplaceholder.typicode.com',
APP: {
htmlAttributes: { lang: 'zh' },
title: '萍宝贝 ES6 项目实战',
titleTemplate: '萍宝贝 ES6 项目实战 - %s',
meta: [
{
name: 'description',
content: 'wikiHong ES6 React 项目模板',
},
],
},
};

2.入口文件
const app = express();

// Use helmet to secure Express with various HTTP headers
app.use(helmet({ contentSecurityPolicy: false }));

// Prevent HTTP parameter pollution
app.use(hpp());

// Compress all requests
app.use(compression());

// Use for http request debug (show errors only)
app.use(logger('dev', { skip: (_, res) => res.statusCode < 400 }));
app.use(favicon(path.resolve(process.cwd(), 'public/logo.png')));
app.use(express.static(path.resolve(process.cwd(), 'public')));

// Enable dev-server in development
if (DEV) devServer(app);

// Use React server-side rendering middleware
app.get('*', ssr);

// @ts-expect-error
app.listen(config.PORT, config.HOST, (error) => {
if (error) console.error(chalk.red(==> 😭 OMG!!! ${error}));
});

3.html渲染
const html = <!doctype html> <html ${head.htmlAttributes.toString()}> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="theme-color" content="#000" /> <link rel="icon" href="/logo.png" /> <link rel="apple-touch-icon" href="/logo192.png" /> <link rel="manifest" href="/manifest.json" /> ${head.title.toString()} ${head.base.toString()} ${head.meta.toString()} ${head.link.toString()} <!-- Insert bundled styles into <link> tag --> ${extractor.getLinkTags()} ${extractor.getStyleTags()} </head> <body> <!-- Insert the router, which passed from server-side --> <div id="react-view">${htmlContent}</div> <!-- Store the initial state into window --> <script> // Use serialize-javascript for mitigating XSS attacks. See the foll // http://redux.js.org/docs/recipes/ServerRendering.html#security-co window.__INITIAL_STATE__=${serialize(initialState)}; </script> <!-- Insert bundled scripts into <script> tag --> ${extractor.getScriptTags()} ${head.script.toString()} </body> </html>;

const minifyConfig = {
collapseWhitespace: true,
removeComments: true,
trimCustomFragments: true,
minifyCSS: true,
minifyJS: true,
minifyURLs: true,
};

// Minify HTML in production
return DEV ? html : minify(html, minifyConfig);

4.本地服务配置
export default (app: Express): void => {
const webpack = require("webpack");
const webpackConfig = require("../../webpack/client.config").default;
const compiler = webpack(webpackConfig);
const instance = require("webpack-dev-middleware")(compiler, {
headers: { "Access-Control-Allow-Origin": "" },
serverSideRender: true,
});
app.use(instance);
app.use(
require("webpack-hot-middleware")(compiler, {
log: false,
path: "/__webpack_hmr",
heartbeat: 10
1000,
})
);
instance.waitUntilValid(() => {
const url = http://${config.HOST}:${config.PORT};
console.info(chalk.green(==> 🌎 Listening at ${url}));
});
};

5.SSR配置
export default async (
req: Request,
res: Response,
next: NextFunction
): Promise => {
const { store } = createStore({ url: req.url });

// The method for loading data from server-side
const loadBranchData = (): Promise => {
const branch = matchRoutes(routes, req.path);
const promises = branch.map(({ route, match }) => {
if (route.loadData)
return Promise.all(
route
.loadData({
params: match.params,
getState: store.getState,
req,
res,
})
.map((item: Action) => store.dispatch(item))
);
return Promise.resolve(null);
});
return Promise.all(promises);
};

try {
// Load data from server-side first
await loadBranchData();
const statsFile = path.resolve(process.cwd(), "public/loadable-stats");
const extractor = new ChunkExtractor({ statsFile });
const staticContext: Record = {};
const App = extractor.collectChunks(

{/ Setup React-Router server-side rendering /}

{renderRoutes(routes)}


);
const initialState = store.getState();
const htmlContent = renderToString(App);
// head must be placed after "renderToString"
// see: https://github.com/nfl/react-helmet#server-usage
const head = Helmet.renderStatic();

// Check if the render result contains a redirect, if so we need to set
// the specific status and redirect header and end the response
if (staticContext.url) {
    res.status(301).setHeader("Location", staticContext.url);
    res.end();

    return;
}

// Pass the route and initial state into html template, the "statusCode" c
res
    .status(staticContext.statusCode === "404" ? 404 : 200)
    .send(renderHtml(head, extractor, htmlContent, initialState));

} catch (error) {
res.status(404).send("Not Found :(");
console.error(chalk.red(==> 😭 Rendering routes error: ${error}));
}
next();
};

5.构建工具处理
1.基础配置
const config = (isWeb: boolean):Configuration =>({
mode: isDev ? 'development' : 'production',
context: path.resolve(process.cwd()), // 上下文中的传递
// 压缩大小, 性能优化
optimization: {
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true // 保留console的内容
}
}
})
]
},
plugins: getPlugins(isWeb) as WebpackPluginInstance[],
module: {
// 解析对应的loader
rules: [
{
test: /.(t|j)sx?$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
caller: { target: isWeb ? 'web' : 'node' },
cacheDirectory: isDev,
},
},
{
test: /.css$/,
use: getStyleLoaders(isWeb),
},
{
test: /.(scss|sass)$/,
use: getStyleLoaders(isWeb, true),
},
{
test: /.(woff2?|eot|ttf|otf)$/i,
type: 'asset',
generator: { emit: isWeb },
},
{
test: /.(png|svg|jpe?g|gif)$/i,
type: 'asset',
generator: { emit: isWeb },
},
]
}
})

export default config;

getStyleLoaders 配置
// loader style-loader postcss-loader

const getStyleLoaders = (isWeb: boolean, isSaas?: boolean) => {
let loaders: RuleSetUseItem[] = [
{
loader: 'css-loader',
options: {
importLoaders: isSaas ? 2 : 1,
modules: {
auto: true,
exportOnlyLocals: !isWeb, // ssr
}
}
},
{
loader: 'postcss-loader',
}
];

if (isWeb) {
loaders = [
...loaders,
]
}

if (isSaas)
loaders = [
...loaders,
{
loader: 'sass-loader',
}
];

return loaders
}

getPlugins 配置
// plugins csr ssr
const getPlugins = (isWeb: boolean) => {
let plugins = [
new webpack.ProgressPlugin(),
// 适用于SSR服务下的manifest信息
new WebpackManifestPlugin({}), // 改变ssr返回页面的title
new LoadablePlugin({
writeToDisk: true,
filename: '../loadable-state.json', // 声明写入文件中的名称
}),
// 定义全局变量
new webpack.DefinePlugin({
CLIENT: isWeb,
SERVER: !isWeb,
DEV: isDev,
})
];

// 根据process,env NODE_ENV analyze
if (!isDev) {
plugins = [
...plugins,
new BundleAnalyzerPlugin({
analyzerMode: process.env.NODE_ENV === 'analyze' ? 'server' : 'disabled'
}),
];
}
return plugins
}

2.客户端配置
const config:Configuration = {
devtool: isDev && 'eval-cheap-source-map',
entry: './src/client',
output: {
filename: isDev ? '[name].js' : '[name].[contenthash].js',
chunkFilename: isDev ? '[id].js' : '[id].[contenthash].js',
path: path.resolve(process.cwd(), 'public/assets'),
publicPath: '/assets/',
},
optimization: {
minimizer: [new CssMinimizerPlugin()]
},
plugins: getPlugins()
}

export default merge(baseConfig(true), config)

getPlugins 配置

const getPlugins = () => {
let plugins = []

if (isDev) {
plugins = [
...plugins,
// 热更新
new webpack.HotModuleReplacementPlugin(),
// react refresh
new ReactRefreshWebpackPlugin(),
]
}
return plugins;
}
3.服务器端配置
const config: Configuration = {
target: 'node',
devtool: isDev ? 'inline-source-map' : 'source-map',
entry: './src/server',
output: {
filename: 'index.js',
chunkFilename: '[id].js',
path: path.resolve(process.cwd(), 'public/server'),
libraryTarget: 'commonjs2',
},
node: { dirname: true, filename: true },
externals: [
'@loadable/component',
nodeExternals({
// Load non-javascript files with extensions, presumably via loaders
allowlist: [/.(?!(?:jsx?|json)$).{1,5}$/i],
}),
] as Configuration['externals'],
plugins: [
// Adding source map support to node.js (for stack traces)
new webpack.BannerPlugin({
banner: 'require("source-map-support").install();', // 最新的更新时间
raw: true,
}),
],
};

相关文章
|
23天前
|
前端开发 JavaScript
React学习之——条件渲染
【10月更文挑战第16天】React 中没有像Vue中v-if这种指令。React 中的条件渲染和 JavaScript 中的一样,使用 JavaScript 运算符 if 或者条件运算符去创建元素来表现当前的状态,然后让 React 根据它们来更新 UI。
|
17天前
|
监控 前端开发 JavaScript
React 静态网站生成工具 Next.js 入门指南
【10月更文挑战第20天】Next.js 是一个基于 React 的服务器端渲染框架,由 Vercel 开发。本文从基础概念出发,逐步探讨 Next.js 的常见问题、易错点及解决方法,并通过具体代码示例进行说明,帮助开发者快速构建高性能的 Web 应用。
45 10
|
16天前
|
资源调度 前端开发 数据可视化
构建高效的数据可视化仪表板:D3.js与React的融合之道
【10月更文挑战第25天】在数据驱动的时代,将复杂的数据集转换为直观、互动式的可视化表示已成为一项至关重要的技能。本文深入探讨了如何结合D3.js的强大可视化功能和React框架的响应式特性来构建高效、动态的数据可视化仪表板。文章首先介绍了D3.js和React的基础知识,然后通过一个实际的项目案例,详细阐述了如何将两者结合使用,并提供了实用的代码示例。无论你是数据科学家、前端开发者还是可视化爱好者,这篇文章都将为你提供宝贵的洞见和实用技能。
35 5
|
14天前
|
前端开发 JavaScript 安全
学习如何为 React 组件编写测试:
学习如何为 React 组件编写测试:
32 2
|
17天前
|
存储 JavaScript 前端开发
JS的ES6知识点
【10月更文挑战第19天】这只是 ES6 的一些主要知识点,ES6 还带来了许多其他的特性和改进,这些特性使得 JavaScript 更加现代化和强大,为开发者提供了更多的便利和灵活性。
15 3
|
22天前
|
JavaScript 测试技术 API
跟随通义灵码一步步升级vue2(js)项目到vue3版本
Vue 3 相较于 Vue 2 在性能、特性和开发体验上都有显著提升。本文介绍了如何利用通义灵码逐步将 Vue 2 项目升级到 Vue 3,包括备份项目、了解新特性、选择升级方式、升级依赖、迁移组件和全局 API、调整测试代码等步骤,并提供了注意事项和常见问题的解决方案。
|
25天前
|
JavaScript 前端开发 测试技术
JavaScript与TypeScript:为何TypeScript成为大型项目的首选
JavaScript与TypeScript:为何TypeScript成为大型项目的首选
28 1
|
28天前
|
JavaScript 前端开发 索引
JavaScript ES6及后续版本:新增的常用特性与亮点解析
JavaScript ES6及后续版本:新增的常用特性与亮点解析
24 4
|
26天前
|
人工智能 JavaScript 网络安全
ToB项目身份认证AD集成(三完):利用ldap.js实现与windows AD对接实现用户搜索、认证、密码修改等功能 - 以及针对中文转义问题的补丁方法
本文详细介绍了如何使用 `ldapjs` 库在 Node.js 中实现与 Windows AD 的交互,包括用户搜索、身份验证、密码修改和重置等功能。通过创建 `LdapService` 类,提供了与 AD 服务器通信的完整解决方案,同时解决了中文字段在 LDAP 操作中被转义的问题。
|
29天前
|
资源调度 前端开发 JavaScript
React进阶学习
React进阶学习
11 1