摘要:单页架构方案当下虽然很时髦,不过大多数的网站依旧选择多页或者单页多页的混合架构。在正式环境中直接配置进行转发补充本文抛砖引玉简单搭建了一个前后端分离框架,但还有很多不完善的地方。
先上项目SPA(单页架构)方案当下虽然很时髦,不过大多数的网站依旧选择多页或者单页+多页的混合架构。使用 express, webpack 本文低成本的实现了包含多页架构,自动刷新,前后端分离 等概念
git repo
node-pages-webpack-hot
开发
npm install npm install supervisor -g npm run start # 开发环境,配置 hot reload npm run prod # 生产环境 npm run build # 编译前端生产环境
DEMO
FE目录:
SERVER目录:
为了不浪费你的时间,在阅读以下内容时需要有:
express 基础知识,以及对 node 简单了解
webpack 中级了解,本文采用 webpack2 实现
1. FE 端配置前端配置需要实现的功能点:
多页架构自动生成 entry,并通过 html-webpack-plugin 生成每个页面的模板,且选择任意模板引擎需要实现 layout 模板功能(本文使用swig作为模板引擎)
配置各种文件后缀的 loader
使用 HotModuleReplacementPlugin 实现修改自刷新
1.1 自动分析entry规定每个页面必须有一个同名的 js 文件作为此页面的 entry ,目录深度可变,如下图,分解为两个 entry:
为实现自动化获取,使用了 glob 获取所有 .js 文件,并判断是否有同名的 .html ,如果有则生成一个 entry,如果是 dev 环境则多增加 hotMiddlewareScript 模块
// get all js files let files = glob.sync(config.src + "/**/*.js"); let srcLength = config.src.length; let entrys = {}; files.forEach(function (_file) { let file = path.parse(_file); let htmlFile = path.resolve(file.dir, file.name + "." + config.ext); // if has same name template file, it is a entry if (fs.existsSync(htmlFile)) { let pathIndex = file.dir.indexOf(config.src); if (config.dev == "dev") { entrys[config.staticRoot + file.dir.slice(srcLength) + "/" + file.name] = [path.resolve(_file), hotMiddlewareScript]; } else { entrys[config.staticRoot + file.dir.slice(srcLength) + "/" + file.name] = path.resolve(_file); } } }); return entrys;1.2 自动生成 html-webpack-plugin 模板
生成一系列 HtmlWebpackPlugin 的要点如下:
获取到所有的 .html 后,判断是否有对应的 entry 文件,若有则创建 HtmlWebpackPlugin
如果页面为 layout 模板,则需要多注入由 CommonsChunkPlugin 生成的 common 模块
自动生成 HtmlWebpackPlugin 代码如下:
let htmls = []; // get all templates let files = glob.sync(config.src + "/**/*." + config.ext); let srcLength = config.src.length; files.forEach(function (_file) { let file = path.parse(_file); let chunks = []; let chunkName = config.staticRoot + file.dir.slice(srcLength) + "/" + file.name; // if has same name entry, create a html plugin let c = entrys[chunkName]; c && chunks.push(chunkName); // layout will contains common chunk if (file.name == config.serverLayoutName) { chunks.push(config.staticRoot + "/common"); } let plugin = new HtmlWebpackPlugin({ filename: config.templateRoot + file.dir.slice(srcLength) + "/" + file.base, template: path.resolve(file.dir, file.base), chunks: chunks, inject: false }); htmls.push(plugin); }); return htmls;
由于引入了模板 extends 支持,需设置 inject=false 便不会自动注入 assets 文件
编写 webpack 插件,将页面的 js assets, css assets 分别注入到:
两个替换文案处,例如页面模板:
{% extends "../base/base.html" %} {% block title %}My Page{% endblock %} {% block style %}{%endblock%} {% block head %} {% parent %} {% endblock %} {% block content %}This is just an home page!!!
cloudslink page2 {% endblock %} {% block script %}{%endblock%}
编译后替换后为:
{% extends "../base/base.html" %} {% block title %}My Page{% endblock %} {% block style %}{%endblock%} {% block head %} {% parent %} {% endblock %} {% block content %}1.3 各种 loader 配置,提取页面 cssThis is just an home page!!!
cloudslink page2 {% endblock %} {% block script %}{%endblock%}
在 dev 环境下由于配置了 webpack-hot-middleware 所以不能对 css 进行提取,否则无法热更新
样式相关的 loader 配置如下:
var extractInstance = new ExtractTextPlugin("[name].css"); if (config.env == "dev") { var stylusLoader = [ { loader: "style-loader" }, { loader: "css-loader" }, { loader: "stylus-loader" } ]; var cssLoader = [ { loader: "style-loader" }, { loader: "css-loader" } ]; } else { var stylusLoader = extractInstance.extract(["css-loader", "stylus-loader"]); var cssLoader = extractInstance.extract(["css-loader"]); }
并将所有的 loader 放到同一个文件进行维护:
var rules = [ { test: /.styl$/, exclude: /node_modules/, use: stylusLoader }, { test: /.css$/, exclude: /node_modules/, use: cssLoader }, { test: /.html$/, use: { loader: "html-loader", options: { minimize: false } } }, ...... ...... ]1.4 路径配置
对生成模板,静态文件输出目录进行统一控制,便于结合各种后端架构
const port = process.env.PORT || 8080; const env = process.env.NODE_ENV || "dev"; const CONFIG_BUILD = { env: env, ext: "html", // tempate ext src: path.resolve(__dirname, "../src"), // source code path path: env == "dev" ? "/" : path.resolve(__dirname, "../dist"), // base output path templateRoot: "templates", // tempate output path staticRoot: "static", // static output path serverLayoutName: "base", // swig layout name , only one file publicPath: env == "dev" ? ("http://localhost:" + port + "/") : "/" }2. SERVER 端配置
server 端搭建了 express 服务,实现的功能点如下:
使用 webpack-dev-middleware 进行 webpack 编译
使用 webpack-hot-middleware 实现 hot reload
使用 supervisor 服务监听 node 文件改动并自动重启
render 模板时将内存中的文件写入硬盘,以进行渲染
2.1 webpack 接入 express生成 webpack 的 compiler
var webpack = require("webpack"), webpackDevConfig = require(path.resolve(config.root, "./fe/webpack.config.js")); var compiler = webpack(webpackDevConfig);
将 compiler 作为 express 的中间件
// attach to the compiler & the server app.use(webpackDevMiddleware(compiler, { // public path should be the same with webpack config publicPath: webpackDevConfig.output.publicPath, noInfo: false, stats: { colors: true } }));
其中 publicPath 指明了 assets 请求的根路径,这里配置的是:http://localhost:8080/
2.2 hot reload 方案 2.2.1 js,css 修改自刷新js、css 的自刷新通过配置 webpack-hot-middleware 实现(fe 也需进行相应的配置)
// server const webpackHotMiddleware = require("webpack-hot-middleware"); app.use(webpackHotMiddleware(compiler)); // fe webpackPlugins.push( new webpack.HotModuleReplacementPlugin() );2.2.2 node 修改自刷新
node 文件修改通过配置 supervisor 服务实现自动刷新
安装服务:
npm install supervisor -g
配置启动参数:
// package.json "scripts": { "start": "cross-env NODE_ENV=dev supervisor -w server -e fe server/server.js" }
supervisor 监听了 server 文件夹下所有的改动,改动后重启 express服务。
想要实现浏览器自动刷新,需要在 layout 模板加入如下代码:
{% if env == "dev" %} {% endif %}2.3 对 template 进行 render
当 webpack 作为 express 中间件时,生成的所有文件都存在内存中,当然也包括由 html-webpack-plugin 生成的模板文件。
然而 express 的 render 函数只能指定一个存在于文件系统中的模板, 即dev 环境下 render 模板前需要将其从内存中取得并存放到文件系统中。
module.exports = (res, template) => { if (config.env == "dev") { let filename = compiler.outputPath + template; // load template from compiler.outputFileSystem.readFile(filename, function(err, result) { let fileInfo = path.parse(path.join(config.templateRoot, filename)); mkdirp(fileInfo.dir, () => { fs.writeFileSync(path.join(config.templateRoot, filename), result); res.render(template); }); }); } else { res.render(template); } }
layout 模板的存储需要一个中间件:
app.use((req, res, next) => { let layoutPath = path.join(config.templateRoot, config.layoutTemplate); let filename = compiler.outputPath + config.layoutTemplate; compiler.outputFileSystem.readFile(filename, function(err, result) { let fileInfoLayout = path.parse(layoutPath); mkdirp(fileInfoLayout.dir, () => { fs.writeFileSync(layoutPath, result); next(); }); }); });
其余的均为 express 基础使用,参阅文档即可
2.4 代理后端接口在dev环境时使用 http-proxy-middleware 对后端接口进行代理:
// set proxy app.use("/api", proxy({target: config.proxy, changeOrigin: true}));
所有 /api 的请求都会代理到 config.proxy 配置的 ip 端口。
在正式环境中直接配置 nginx 进行转发
补充本文抛砖引玉简单搭建了一个前后端分离框架,但还有很多不完善的地方。真实的线上应用还需要考虑 nodejs 运维成本,日志,监控等等。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/86884.html
摘要:回到纯静态页面开发阶段,让页面不需要后端渲染也能跑起来。改造开始本文着重介绍如何将静态页面改造成后端渲染需要的模板。总结在后端渲染的项目里使用多页应用架构是绝对可行的,可不要给老顽固们吓唬得又回到传统前端架构了。 本文首发于Array_Huang的技术博客——实用至上,非经作者同意,请勿转载。原文地址:https://segmentfault.com/a/119000000820338...
摘要:回到纯静态页面开发阶段,让页面不需要后端渲染也能跑起来。改造开始本文着重介绍如何将静态页面改造成后端渲染需要的模板。总结在后端渲染的项目里使用多页应用架构是绝对可行的,可不要给老顽固们吓唬得又回到传统前端架构了。 本文首发于Array_Huang的技术博客——实用至上,非经作者同意,请勿转载。原文地址:https://segmentfault.com/a/119000000820338...
摘要:本文首发于的技术博客实用至上,非经作者同意,请勿转载。原文地址如果您对本系列文章感兴趣,欢迎关注订阅这里前言书承上文多页应用架构系列十如何打造一个自定义的。终于,发现了这一大杀器,打包时间过长的问题得到完美解决。 本文首发于Array_Huang的技术博客——实用至上,非经作者同意,请勿转载。原文地址:https://segmentfault.com/a/119000000710437...
摘要:浅谈秒杀系统架构设计后端掘金秒杀是电子商务网站常见的一种营销手段。这两个项目白话网站架构演进后端掘金这是白话系列的文章。 浅谈秒杀系统架构设计 - 后端 - 掘金秒杀是电子商务网站常见的一种营销手段。 不要整个系统宕机。 即使系统故障,也不要将错误数据展示出来。 尽量保持公平公正。 实现效果 秒杀开始前,抢购按钮为活动未开始。 秒杀开始时,抢购按钮可以点击下单。 秒杀结束后,按钮按钮变...
阅读 2809·2021-10-26 09:48
阅读 1671·2021-09-22 15:22
阅读 4028·2021-09-22 15:05
阅读 608·2021-09-06 15:02
阅读 2606·2019-08-30 15:52
阅读 2106·2019-08-29 18:38
阅读 2754·2019-08-28 18:05
阅读 2332·2019-08-26 13:55