{"id":19285326,"url":"https://github.com/folger-fan/ifelse-loader","last_synced_at":"2025-04-22T03:32:50.003Z","repository":{"id":143821242,"uuid":"97592329","full_name":"folger-fan/ifelse-loader","owner":"folger-fan","description":"ifelse-loader,write common code for frontend and backend","archived":false,"fork":false,"pushed_at":"2017-07-18T12:32:02.000Z","size":16,"stargazers_count":7,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-01T18:52:48.556Z","etag":null,"topics":["node","webpack"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/folger-fan.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-07-18T11:53:57.000Z","updated_at":"2017-12-11T04:40:24.000Z","dependencies_parsed_at":null,"dependency_job_id":"a2259148-ca86-42d2-9f8f-195bd99914e5","html_url":"https://github.com/folger-fan/ifelse-loader","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/folger-fan%2Fifelse-loader","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/folger-fan%2Fifelse-loader/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/folger-fan%2Fifelse-loader/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/folger-fan%2Fifelse-loader/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/folger-fan","download_url":"https://codeload.github.com/folger-fan/ifelse-loader/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250167613,"owners_count":21386004,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["node","webpack"],"created_at":"2024-11-09T21:44:59.870Z","updated_at":"2025-04-22T03:32:49.988Z","avatar_url":"https://github.com/folger-fan.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# node前后端同构新思路\n## 背景\n\n用node做web可以让部分代码前后端通用。例如一些工具方法、部分业务逻辑、M层数据结构等。用node做web的直出，可以共用V层模版。\n\n## 痛点\n\n但是在前后端代码复用的时候不可避免会碰到一些问题，如：\n\n* **环境不同** 前端有window等浏览器api，node端有node的一些api。前端使用useragent从location取，后端使用useragent从请求request中取\n\n* **逻辑不同**  日志上报、鉴权\n\n* **类库不同**  node端调接口的请求的类库和前端ajax的类库不同\n\n网上看过一些用node做直出，前后端同构的文章，大多介绍的是用react、vue等模版引擎的V层模版的复用。\n\n我们想前后端尽量多的共用代码，但现实情况是，很多功能相同的代码不得不写两份，前后端分开引用。很难做到相同的功能在业务层引用同一份文件。\n\n## 新思路\n\n是否可以在源文件中，包含前后端各自代码，用条件判断语句包裹，在编译的过程中根据配置对源文件中内容进行选择过滤，前端引用为前端生成的文件，\n后端可以引用源文件也可以引用为后端编译的文件。虽然也有点麻烦，但能更进一步通用代码，对业务层代码透明。\n\n### 实现\n笔者现在用的webpack编译前端文件，在github上搜到了类似功能的loader\n [if-loader](https://github.com/friskfly/if-loader)\n[ifdef-loader](https://github.com/nippur72/ifdef-loader)\n其中if-loader只能判断if，没有判断else的功能。\n\nifdef-loader用ts实现的，可能是为webpack1准备的，options只能在loader?后面追加条件。相比if-loader增加了else的判断。如下所示：\n```\n///#if !node\nconsole.log('if not node ');\n///#else\nconsole.log('else not node');\n///#endif\n```\n会被编译成\n```\n////////////\n////////////////////////////\n////////\nconsole.log('else not node');\n/////////\n```\n但是在node端如果引用源文件的话，为前端准备的代码会遗留在源文件中造成代码冗余。有些前端文件在es6的标准些会使用import等node不支持的语法，会造成报错。\n所以笔者在ifdef-loader的基础上做了完善，新写了[ifelse-loader](./ifelse-loader)：保留ifdef-loader的功能的基础上，options可以在webpack配置文件中用options配置，不用追加在loader?后的参数中。\n#### 配置\n```\n{\n   loader: '../ifelse-loader',\n     options: {\n        node: false\n     }\n}\n```\n\n增加了在///#code 后写代码的功能，\n```\n///#if node\nconsole.log('if node 2');\n///#else\n///#code console.log('else node 2')\n///#endif\n```\n\n会被编译成\n\n```\nconsole.log('if node 2');\n```\n\n#### 用法举例\n源文件\n```\n///#if node\nconsole.log('if node 1');\n///#endif\n///#if !node\nconsole.log('if not node ');\n///#else\nconsole.log('else not node');\n///#endif\n\n///#if node\nconsole.log('if node 2');\n///#else\n///#code console.log('else node 2')\n///#endif\n\n///#if !node\n///#code console.log('if not node ');\n///#else\n///#code console.log('else not node');\n///#endif\n```\n编译后\n```\nconsole.log('if node 1');\n\nconsole.log('else not node');\n\nconsole.log('if node 2');\n\nconsole.log('else not node')\n```\n如果用 `///#code console.log('else node 2')`这种写法，代码写起来可能会比较痛苦。\n\n笔者尝试过将node端也用webpack编译一份，也走的通，不过感觉用起来很奇怪。\n后来想将node中的require改写，可以带上编译的功能或者webpack在编译的时候将源文件a编译一份放到当前目录生成_node_a文件，\n其他文件引用a的时候会转而引用_node_a。因为node中每个模块的require都是独立的，没有统一的地方改写，所以另外写了个[ISRequire](https://github.com/folger-fan/ifelse-loader/blob/master/ISRequire.js)包装原require，\n[具体用法可参照test下的文件](https://github.com/folger-fan/ifelse-loader/tree/master/test)。\n###  实际案例\n####  art-template的filter\n因为正在做的h5页面主要是内容展示，不涉及数据修改，所以没有采用react、vue，而是选用一个轻量模版引擎[art-template](https://aui.github.io/art-template/)\n因为前后端用模版生成html需要注册工具方法，但前后端api不同。前端需要引入一个*art-template/lib/runtime*但后端不需要，前端ua和后端ua取法不一样，取ur参数的方法也不一样，所以用ifelse-loader做适配。如下是选取的部分代码\n```\nlet template;\nlet runtime = {};\n///#if node\ntemplate = require('art-template');\nlet QS = require('querystring');\nfunction getFullUrl(path, params) {\n    var basePath = getBasePath();\n    var baseQuery = getBaseQuery();\n    var params = params || {};\n    for (var i = 0; i \u003c keyMap.length; i++) {\n        var key = keyMap[i];\n        if (baseQuery.hasOwnProperty(key) \u0026\u0026 !params.hasOwnProperty(key)) {\n            params[key] = baseQuery[key];\n        }\n    }\n    var querystring = QS.encode(params);\n    return basePath + path.replace(/(\\?|#)(.+)/g, '') + (querystring ? '?' : '') + querystring;\n}\nfunction getBaseQuery() {\n    return template.req.query;\n}\n\nfunction getBasePath() {\n    var basePath = '/n';\n    if (template.req.query.share == '1' || template.req.url.indexOf('/share') == 0) {\n        basePath = '/share';\n    }\n    if (template.req.query.site == '1' || template.req.url.indexOf('/site') == 0) {\n        basePath = '/site';\n    }\n    return basePath;\n}\n\nfunction getUserAgent() {\n    return template.req.headers['user-agent'];\n}\n///#else\ntemplate = require('../views/lib/template.js');\nruntime = require('art-template/lib/runtime');\nlet url = require('../views/lib/url.js')['default'];\nfunction getFullUrl(path, params) {\n    var basePath = getBasePath();\n    var baseQuery = getBaseQuery();\n    var params = params || {};\n    for (var i = 0; i \u003c keyMap.length; i++) {\n        var key = keyMap[i];\n        if (baseQuery.hasOwnProperty(key) \u0026\u0026 !params.hasOwnProperty(key)) {\n            params[key] = baseQuery[key];\n        }\n    }\n    return url.setQuery(url.getBaseUrl(basePath + path), params);\n}\nfunction getBaseQuery() {\n    return url.getBaseQuery();\n}\nfunction getBasePath() {\n    var basePath = '/n';\n    if (location.pathname.indexOf('/share/') == 0) {\n        basePath = '/share';\n    }\n    if (location.pathname.indexOf('/site/') == 0) {\n        basePath = '/site';\n    }\n    return basePath;\n}\nfunction getUserAgent() {\n    return window.navigator.userAgent;\n}\n///#endif\nconsole.log('init template filter');\nconst keyMap = ['menu'];\nlet filters = {\n    grade(grade) {\n        grade = Number(grade);\n        // return Math.floor(grade / 10) + 1;\n        return (grade / 10).toFixed(1);\n    },\n    gradeIcon(grade) {\n        grade = Number(grade);\n        return Math.floor(grade / 10) + 1;\n    },\n    gradeShort(grade) {\n        grade = Number(grade || 0) / 10;\n        return grade.toFixed(1)\n    }\n};\nfor (var key in filters) {\n    template.defaults.imports[key] = runtime[key] = filters[key];\n}\nmodule.exports = template;\n```\n在前端直接引用\n```\nrequire('../../util/template.js');\n```\n在后端用ISRequire引用\n```\nvar ISRequire = require('../util/ISRequire')(require);\nvar template = ISRequire('../util/template');\n```\n#### 接口调用\n我们目前的项目结构，后台提供http接口供运营平台页面、h5页面和客户端调用，node端调后台接口做直出，\nh5首屏不需要的接口通过node转发调后台（因为后台的接口比较零碎，通过node做接口合并），\n运营平台页面和h5页面通过webpack编译。通过ifelse-loader可以同构前后端的接口调用代码，\nrequest-promise和request_promise原先是前后端各自调用接口的代码，现在通过BFRequest封装在一起，对业务层透明\n```\n/**\n * Created by folgerfan on 2017/7/17.\n * backend and frontend request\n */\nlet BaseReq = require('./BaseReq');\n\nlet rp;\n///#if node\nrp = require('request-promise');\n///#else\nrp = require('../views/lib/request_promise');\n///#endif\nlet BFRequest = function(params){\n    params = BaseReq(params);\n    return rp(params)\n};\nmodule.exports = BFRequest;\n```\n前端直接引用\n```\nimport rp from '../../util/BFRequest';\nrp({\n     //参数\n}).then(/*逻辑处理*/)   \n```\n在后端用ISRequire引用\n```\nlet ISRequire = require('../../util/ISRequire')(require);\nlet rp = ISRequire('../../util/BFRequest');\nrp({\n     //参数\n}).then(/*逻辑处理*/)\n```\n\n### 不足\n* 前后端代码写在同一份文件中的时候不能使用 import 等node不支持的语法\n* 因为let重复定义会报错，可以直接使用var或用let先定义后赋值\n* 一定程度上的代码啰嗦和不好看\n### 后记\n感谢部门同事的提议和建议，几番折腾，尝试了多种方案踩了一些坑才沉淀出目前写出来的方案。让我们在write less do more的道路上越走越远。","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffolger-fan%2Fifelse-loader","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffolger-fan%2Fifelse-loader","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffolger-fan%2Fifelse-loader/lists"}