├── .eslintrc.json ├── demos ├── letter │ ├── a.html │ └── a.js ├── main.css ├── fruits.js ├── index.html ├── README.md ├── main.js ├── hello.html ├── socket.io.js └── vendors │ └── jquery-2.2.3.min.js ├── .gitignore ├── 模块加载流程.png ├── .npmignore ├── test ├── test1.html ├── test1.js └── index.html ├── src ├── jtaro.module.config2.js ├── jtaro.module.config.js ├── server.js ├── parse.js └── client.js ├── index.html ├── LICENSE ├── .vscode └── launch.json ├── package.json ├── LOG.md └── README.md /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "standard" 3 | } -------------------------------------------------------------------------------- /demos/letter/a.html: -------------------------------------------------------------------------------- 1 | 2 |
-------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .DS_Store 3 | .git/ 4 | npm-debug.log -------------------------------------------------------------------------------- /模块加载流程.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chjtx/JTaro-Module/HEAD/模块加载流程.png -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | */** 3 | !src/client.js 4 | !src/parse.js 5 | !src/server.js 6 | -------------------------------------------------------------------------------- /demos/main.css: -------------------------------------------------------------------------------- 1 | #main { 2 | width: 420px; 3 | height: 200px; 4 | margin: 0 auto; 5 | } -------------------------------------------------------------------------------- /test/test1.html: -------------------------------------------------------------------------------- 1 | 8 |
123123
9 | -------------------------------------------------------------------------------- /demos/letter/a.js: -------------------------------------------------------------------------------- 1 | import 'jquery' 2 | import html from './a.html' 3 | 4 | console.log(html) 5 | var a = 123444 6 | var b = (i) => 1 + i 7 | console.log(b(1)) 8 | 9 | export default `is:${a}` 10 | -------------------------------------------------------------------------------- /test/test1.js: -------------------------------------------------------------------------------- 1 | /* 2 | import abc from "abc.js" 3 | */ 4 | //asdfaf 5 | import html from './test1.html' 6 | import from '../demos/main.js'; 7 | 8 | document.body.insertAdjacentHTML('beforeend', html) 9 | 10 | 11 | // abc -------------------------------------------------------------------------------- /demos/fruits.js: -------------------------------------------------------------------------------- 1 | import 'jquery' 2 | import a from 'a' 3 | 4 | var apple = 'Apple' 5 | var banana = 'Banana' 6 | 7 | console.log(a) 8 | 9 | export { 10 | apple, 11 | banana, 12 | a 13 | } 14 | export var orange = 'Orange' 15 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | demos 7 | 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /src/jtaro.module.config2.js: -------------------------------------------------------------------------------- 1 | var alias = require('rollup-plugin-alias') 2 | 3 | module.exports = { 4 | website: '../', // 站点目录,以server.js所在路径为基准 5 | entry: '../demos/main.js', // 入口文件,以server.js所在路径为基准 6 | plugins: [alias({ 7 | jquery: './vendors/jquery-2.2.3.min.js', // 以入口文件所在路径为基准 8 | fruits: './fruits.js', 9 | a: './letter/a.js' 10 | })] 11 | } 12 | -------------------------------------------------------------------------------- /demos/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | demos 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | JTaro Module 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /demos/README.md: -------------------------------------------------------------------------------- 1 | # JTaro Module Demo 2 | 3 | ## 运行开发示例 4 | 5 | 首页确保已安装git和nodejs(6或以上) 6 | 7 | ```bash 8 | git clone https://github.com/chjtx/JTaro-Module.git 9 | 10 | cd JTaro-Module 11 | 12 | node src/server.js 13 | ``` 14 | 15 | 在浏览器打开`http://localhost:3000/demos/index.html` 16 | 17 | ## Rollup打包示例 18 | 19 | ```bash 20 | # 安装rollup、rollup-plugin-jtaro-module 21 | npm install 22 | 23 | node build.js 24 | ``` 25 | 26 | 将`build/index.html`拖到浏览器访问 27 | -------------------------------------------------------------------------------- /src/jtaro.module.config.js: -------------------------------------------------------------------------------- 1 | var alias = require('rollup-plugin-paths') 2 | var babel = require('rollup-plugin-babel') 3 | 4 | module.exports = { 5 | website: '../', // 站点目录,以server.js所在路径为基准 6 | entry: '../demos/main.js', // 入口文件,以server.js所在路径为基准 7 | plugins: [alias({ 8 | 'vendors@': './vendors/', 9 | jquery: 'vendors@jquery-2.2.3.min.js', // 以入口文件所在路径为基准 10 | fruits: './fruits.js', 11 | a: './letter/a.js' 12 | }), babel({ 13 | include: '**/a.js', 14 | 'presets': [ 15 | [ 16 | 'es2015', 17 | { 18 | 'modules': false 19 | } 20 | ] 21 | ] 22 | })] 23 | } 24 | -------------------------------------------------------------------------------- /demos/main.js: -------------------------------------------------------------------------------- 1 | /* global echarts $ */ 2 | import './main.css' 3 | import 'vendors@jquery-2.2.3.min.js' 4 | import './vendors/echarts.min.js' 5 | import hello from './hello.html' 6 | import { apple, banana, orange as o } from 'fruits' 7 | 8 | var main = $('#main')[0] 9 | // 基于准备好的dom,初始化echarts实例 10 | var myChart = echarts.init(main) 11 | 12 | // 指定图表的配置项和数据 13 | var option = { 14 | title: { 15 | text: 'ECharts 入门示例' 16 | }, 17 | tooltip: {}, 18 | legend: { 19 | data: ['销量'] 20 | }, 21 | xAxis: { 22 | data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子'] 23 | }, 24 | yAxis: {}, 25 | series: [{ 26 | name: '销量', 27 | type: 'bar', 28 | data: [5, 20, 36, 10, 10, 20] 29 | }] 30 | } 31 | 32 | // 使用刚指定的配置项和数据显示图表。 33 | myChart.setOption(option) 34 | 35 | // Hello JTaro Module 36 | main.insertAdjacentHTML('afterend', hello) 37 | 38 | // Show Fruits 39 | var fruits = '
' + [apple, banana, o].join(', ') + '
' 40 | $('#hello').after(fruits) 41 | -------------------------------------------------------------------------------- /demos/hello.html: -------------------------------------------------------------------------------- 1 | 41 |
42 | 43 |

Hello JTaro Module !!!

44 | 45 |
-------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 BarZu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible Node.js debug attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "server", 11 | "program": "${workspaceRoot}/src/server.js", 12 | "cwd": "${workspaceRoot}", 13 | "args": [ 14 | "--watch=." 15 | ] 16 | },{ 17 | "type": "node", 18 | "request": "launch", 19 | "name": "build", 20 | "program": "${workspaceRoot}/build.js", 21 | "cwd": "${workspaceRoot}" 22 | }, 23 | { 24 | "type": "node", 25 | "request": "launch", 26 | "name": "启动程序", 27 | "program": "${workspaceRoot}/src/server.js", 28 | "cwd": "${workspaceRoot}" 29 | }, 30 | { 31 | "type": "node", 32 | "request": "attach", 33 | "name": "附加到进程", 34 | "port": 5858 35 | } 36 | ] 37 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jtaro-module", 3 | "version": "0.3.4", 4 | "description": "Explain the ES6 module to Es5 syntax", 5 | "main": "src/server.js", 6 | "scripts": { 7 | "default": "node src/server.js 3001", 8 | "config": "node src/server.js --config=./jtaro.module.config2.js", 9 | "build": "node build.js" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/chjtx/JTaro-Module.git" 14 | }, 15 | "keywords": [ 16 | "jtaro-module" 17 | ], 18 | "author": "BarZu", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/chjtx/JTaro-Module/issues" 22 | }, 23 | "homepage": "https://github.com/chjtx/JTaro-Module#readme", 24 | "devDependencies": { 25 | "babel-preset-es2015": "^6.24.0", 26 | "eslint": "^4.19.1", 27 | "eslint-config-standard": "^11.0.0", 28 | "eslint-plugin-import": "^2.11.0", 29 | "eslint-plugin-node": "^6.0.1", 30 | "eslint-plugin-promise": "^3.7.0", 31 | "eslint-plugin-standard": "^3.1.0", 32 | "rollup": "^0.38.0", 33 | "rollup-plugin-babel": "^2.7.1", 34 | "rollup-plugin-jtaro-module": "^0.1.0", 35 | "rollup-plugin-paths": "0.0.1" 36 | }, 37 | "dependencies": { 38 | "socket.io": "^2.1.0" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /LOG.md: -------------------------------------------------------------------------------- 1 | # 日志 2 | 3 | ## v0.3.4(2018-06-07) 4 | 5 | - 去掉html文件的注释及空行,减少输出的文件体积 6 | 7 | ## v0.3.3(2018-05-31) 8 | 9 | - 更新client.js,添加循环引用检测机制 10 | 11 | ## v0.3.2(2018-05-15) 12 | 13 | - 更新client.js,使得不管html文件是否有style,都将第一个div标识jtaro路径 14 | 15 | ## v0.3.1(2018-05-14) 16 | 17 | - v0.3.0发布失败,重发 18 | 19 | ## v0.3.0(2018-05-14) 20 | 21 | - 添加监听文件变化功能,修改文件后自动刷新浏览器 22 | - 修复windows系统使用alias插件路径错误的问题 23 | - 将client的ajax改成fetch 24 | - 修复export语句不能断行的bug 25 | 26 | ## v0.2.8(2018-01-30) 27 | 28 | - 修复export语句带有`as`字眼变量时解释错误的bug 29 | 30 | ## v0.2.7(2017-07-12) 31 | 32 | - 修复只有export语句没import语句时没生成闭包的问题 33 | 34 | ## v0.2.6(2017-07-02) 35 | 36 | - 修复v0.2.5配置文件带js后缀报错的bug 37 | - 修改返回给前端的export代码格式,使行号尽可能与原文件对应上 38 | 39 | 40 | ## v0.2.5(2017-06-30) 41 | 42 | - 修复配置文件发生错误时没在控制台输出的bug 43 | - 修改返回给前端的import代码格式,使行号尽可能与原文件对应上 44 | 45 | ## v0.2.4(2017-05-10) 46 | 47 | - 如果开启服务时配置了`--config`,当找不到配置文件时报错 48 | - 配置文件允许省略`./` 49 | 50 | ## v0.2.3 (2017-04-28) 51 | 52 | - 修复style样式的`@keyframes`和`@media`被误处理的问题 53 | 54 | ## v0.2.2 (2017-04-19) 55 | 56 | - 支持`this.myclass {}`选择器语法 57 | 58 | ## v0.2.1 (2017-03-29) 59 | 60 | - client.js/server.js/parse.js各子文件版本号修改为与大版本号一致 61 | 62 | ## v0.2.0 (2017-03-24) 63 | 64 | - 支持rollup插件,目前已支持`rollup-plugin-paths`和`rollup-plugin-babel` 65 | 66 | ## v0.1.0 (2017-02-22) 67 | 68 | - 重写依赖加载算法,由堆栈结构改为树形结构,解决多层引入时出现上层回调被误删的问题 69 | 70 | ## v0.0.9 (2017-02-15) 71 | 72 | - 修复重复引入相同脚本加载不稳定的问题 73 | 74 | ## v0.0.8 (2017-02-13) 75 | 76 | - 修复不同页面引入相同带import关键字的脚本没执行回调的问题 77 | 78 | ## v0.0.7 (2017-01-19) 79 | 80 | - 修复一些style解释bug 81 | 82 | ## v0.0.6 (2017-01-12) 83 | 84 | - 修复一些import解释bug 85 | 86 | ## v0.0.5 (2017-01-11) 87 | 88 | - 修复Mac上Chrome无条件执行回调的bug 89 | 90 | ## v0.0.4 (2017-01-08) 91 | 92 | - 完成,实现ES6模块语法管理html/css/js文件 -------------------------------------------------------------------------------- /src/server.js: -------------------------------------------------------------------------------- 1 | /*! JTaro-Module server.js v0.3.0 ~ (c) 2017-2018 Author:BarZu Git:https://github.com/chjtx/JTaro-Module/ */ 2 | const fs = require('fs') 3 | const path = require('path') 4 | const http = require('http') 5 | const url = require('url') 6 | const socketio = require('socket.io') 7 | const jtaroModule = require('./parse') 8 | const cwd = process.cwd() 9 | 10 | let port = 3000 // 默认端口 11 | let configPath = './jtaro.module.config' // 默认配置文件 12 | let watchPath = '' // 默认不监听 13 | let hasConfigFile = false 14 | let fileChangeTime = 0 // 防抖 15 | let socket = null 16 | 17 | // 截取命令 18 | for (var i = 2; i < process.argv.length; i++) { 19 | // 端口 20 | if (/\d/g.test(process.argv[i])) { 21 | port = process.argv[i] 22 | } 23 | // 配置文件 --config="jtaro.module.config.js" 24 | if (/^--config=/.test(process.argv[i])) { 25 | hasConfigFile = true 26 | configPath = process.argv[i].replace('--config=', '') 27 | .replace(/^("|')|("|')$/g, '').trim().replace(/\.js$/, '') 28 | 29 | // 如果不是以../或./开头的文件,自动加上./ 30 | if (!/^(\.\/|\.\.\/)/.test(configPath)) { 31 | configPath = './' + configPath 32 | } 33 | } 34 | // 监听文件 --watch="./src" 35 | if (/^--watch=/.test(process.argv[i])) { 36 | watchPath = process.argv[i].replace('--watch=', '') 37 | .replace(/^("|')|("|')$/g, '').trim() 38 | } 39 | } 40 | 41 | // 如果存在配置文件,使用rollupjs的插件过滤文件内容 42 | let config = {} 43 | fs.access(path.join(__dirname, configPath + '.js'), function (err) { 44 | if (!err) { 45 | config = require(configPath) 46 | } else if (hasConfigFile) { 47 | throw err 48 | } 49 | runServer() 50 | }) 51 | 52 | function runServer () { 53 | const mime = { 54 | 'css': 'text/css', 55 | 'gif': 'image/gif', 56 | 'html': 'text/html', 57 | 'ico': 'image/x-icon', 58 | 'jpeg': 'image/jpeg', 59 | 'jpg': 'image/jpeg', 60 | 'js': 'text/javascript', 61 | 'json': 'application/json', 62 | 'pdf': 'application/pdf', 63 | 'png': 'image/png', 64 | 'svg': 'image/svg+xml', 65 | 'swf': 'application/x-shockwave-flash', 66 | 'tiff': 'image/tiff', 67 | 'txt': 'text/plain', 68 | 'wav': 'audio/x-wav', 69 | 'wma': 'audio/x-ms-wma', 70 | 'wmv': 'video/x-ms-wmv', 71 | 'xml': 'text/xml' 72 | } 73 | 74 | const server = http.createServer((req, res) => { 75 | const parseURL = url.parse(req.url) 76 | let pathname = parseURL.pathname 77 | if (/\/$/.test(pathname)) { 78 | pathname = pathname + 'index.html' 79 | } 80 | let ext = path.extname(pathname) 81 | const realPath = '.' + pathname 82 | ext = ext ? ext.slice(1) : 'unknown' 83 | const contentType = mime[ext] || 'text/plain' 84 | 85 | fs.stat(realPath, (err, stats) => { 86 | if (err) { 87 | res.writeHead(404, { 'Content-Type': 'text/plain' }) 88 | res.write('This request URL ' + pathname + ' was not found on this server.') 89 | res.end() 90 | } else { 91 | fs.readFile(realPath, 'binary', function (err, file) { 92 | if (err) { 93 | res.writeHead(500, { 'Content-Type': 'text/plain' }) 94 | res.end(err.message) 95 | } else { 96 | if (ext === 'js') { 97 | config.id = path.resolve(__dirname, config.website || '', realPath) 98 | config.entry = path.resolve(__dirname, config.entry || '') 99 | config.is_jtaro_module = true // 标记给rollup-plugin-paths插件使用 100 | file = jtaroModule(file, req.url, config) 101 | } 102 | 103 | res.writeHead(200, { 'Content-Type': contentType }) 104 | res.write(file, 'binary') 105 | res.end() 106 | } 107 | }) 108 | } 109 | }) 110 | }) 111 | 112 | const io = socketio(server) 113 | server.listen(port) 114 | 115 | io.on('connection', function (s) { 116 | socket = s 117 | }) 118 | 119 | // 监听文件变化 120 | function watchFiles (dir) { 121 | const w = fs.watch(dir, (event, filename) => { 122 | const p = path.resolve(dir, filename) 123 | if (event === 'rename') { 124 | fs.stat(p, (err, state) => { 125 | if (!err) { 126 | if (state.isDirectory()) { 127 | w.close() 128 | watchFiles(p) 129 | } 130 | } else { 131 | w.close() 132 | } 133 | }) 134 | } else { 135 | // event === change 136 | fs.stat(p, (err, state) => { 137 | const now = Date.now() 138 | if (!err && state.isFile() && (now - fileChangeTime > 100)) { 139 | fileChangeTime = now 140 | if (socket) { 141 | socket.emit('fileChange') 142 | } 143 | } 144 | }) 145 | } 146 | }) 147 | w.on('error', (e) => { 148 | console.error(e) 149 | }) 150 | // 递归监听 151 | fs.readdirSync(dir).forEach(f => { 152 | const p = path.resolve(dir, f) 153 | fs.stat(p, (err, state) => { 154 | if (err) throw err 155 | if (state.isDirectory()) { 156 | watchFiles(p) 157 | } 158 | }) 159 | }) 160 | } 161 | if (watchPath) { 162 | watchFiles(path.resolve(cwd, watchPath)) 163 | } 164 | } 165 | 166 | console.log('JTaro Module Server run on port: ' + port) 167 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JTaro Module 2 | 3 | JTaro Module是一款使用ES6模块语法的前端模块管理工具,其本身是为更好地服务JTaro而设计,但并不依赖JTaro,完全可以独立运行。 4 | 5 | ## 相关视频教程 6 | 7 | JTaro Tutorial:[https://github.com/chjtx/JTaro-Tutorial](https://github.com/chjtx/JTaro-Tutorial) 8 | 9 | ## 前言 10 | 11 | - 如果你想使用ES6模块语法管理代码,又不想使用webpack这个重型工具 12 | - 如果你只是想简简单单的写个js、html、css,不需要typescript、postcss等高级工具 13 | - 如果你想开发时所见到的错误就像使用script标签引入的脚本一样清晰 14 | - 如果你想上线代码只打包成一个或几个文件以减少文件体积和连接数 15 | 16 | 那么,你可以继续往下读了! 17 | 18 | ## 特点 19 | 20 | - 轻盈易用,几个文件,数百行代码,只需要开启其nodejs服务即可使用ES6模块语法编写代码,无需Babel转译 21 | - 方便排错,浏览器展示代码与本地js文件一一对应,错误行号一目了然 22 | - 低耗高能,只需要安装nodejs 6以上版本即可运行,在3000元windows机上跑也是扛扛的 23 | - 代码精简,上线代码使用Rollup.js打包,除寥寥几行用于处理样式的代码外,不带任何模块管理的代码 24 | 25 | ## 示例 26 | 27 | [运行示例](https://github.com/chjtx/JTaro-Module/tree/master/demos) 28 | 29 | ## 开始使用 30 | 31 | ### 开发模式 32 | 33 | 1. 安装`npm install -D jtaro-module` 34 | 2. 在自己的项目目录里使用命令行(终端)运行`node node_modules/jtaro-module/src/server.js`,开启本地静态文件服务,默认为3000端口,可自定义端口`node node_modules/jtaro-module/src/server.js 3030` 35 | 3. 在index.html的head引入`node_modules/jtaro-module/src/client.js`,在body最后引入入口文件(只要是js文件都可当作入口文件),JTaro Module将会从入口文件开始加载所有依赖文件 36 | 4. 在浏览器上运行`localhost:3000/index.html`,所有js文件都会被拦截,所有符合条件的import/export将会被转换 37 | 5. 监听文件变化自动刷新浏览器`node node_modules/jtaro-module/src/server.js --watch=.`,watch后面参数是以`.`开头的相对路径,相对于当前执行命令的目录,例:`.`、`..`、`./src`,该路径表示要监听的目录。 38 | 6. 若需要监听文件变化自动刷新浏览器,必须自行将客户端的`socket.io.js`在`client.js`之前引入。了解socket.io请看[https://socket.io/docs/](https://socket.io/docs/) 39 | 40 | 建议使用[Visual Studio Code](https://code.visualstudio.com/)进行开发,可直接在编辑器开启nodejs服务 41 | 42 | ### 上线模式 43 | 44 | 1. 安装rollup、引入`rollup-plugin-jtaro-module`添加到rollup的插件里,打包入口文件 45 | 2. 拷贝index.html到build/并删除拷贝的index.html里的`node_modules/jtaro-module/src/client.js` 46 | 3. `node build.js` 47 | 48 | 与Rollup.js更多相关内容不在本页范围内,请自行谷歌/百度。 49 | 50 | build.js大概代码长这样 51 | 52 | ```js 53 | var rollup = require('rollup') 54 | var path = require('path') 55 | var jtaroModule = require('rollup-plugin-jtaro-module') 56 | 57 | rollup.rollup({ 58 | entry: path.resolve('demos/main.js'), 59 | plugins: [jtaroModule({ root: 'demos' })] 60 | }).then(function (bundle) { 61 | bundle.write({ 62 | format: 'iife', 63 | dest: 'build/main.js' 64 | }) 65 | }) 66 | ``` 67 | 68 | ## JTaro Module 运行原理 69 | 70 | ### 处理js 71 | 72 | 本地开启nodejs静态服务,拦截所有js文件,检测文件内容,将import/export解释成ES5可执行的方法,再返回给浏览器 73 | 74 | 例: 75 | 76 | ```js 77 | // main.js 78 | import { a } from './a.js' 79 | 80 | console.log(a) 81 | 82 | export default { 83 | a: a 84 | } 85 | ``` 86 | 87 | 浏览器接收到的内容为: 88 | 89 | ```js 90 | (function (f) { 91 | JTaroAssets['/main.js'] = 1 92 | var g = {count:1} 93 | g.callback = function () { f.apply(null, [ 94 | JTaroModules['/a.js'].default 95 | ]) } 96 | JTaroLoader.import('./a.js', g) 97 | })(function (a) { 98 | // main.js 99 | 100 | console.log(a) 101 | 102 | JTaroModules['/main.js'].default = { 103 | a: a 104 | } 105 | }) 106 | ``` 107 | 108 | ### 处理html 109 | 110 | 当引入的文件为html时,JTaro Module会将html里的style在head里生成样式表,其余内容以字符串形式返回。JTaro是基于Vue开发的,因此JTaro Module的html内容也应该遵循Vue的模板规则,最外层只有一个dom元素。另外,html文件里只允许一个style标签 111 | 112 | 推荐 113 | 114 | ```html 115 | 118 |
119 |

最外层只有一个div

120 |
121 | ``` 122 | 123 | 不推荐 124 | 125 | ```html 126 | 129 |
130 |

最外层只有一个div

131 |
132 |
133 | 我是最外层的第二个div 134 |
135 | ``` 136 | 137 | JTaro Module会将style和div(dom元素)分离,并在第一个div加上与style对应的标识,以达到作用域限定的目的。如果你要给第一个div加样式,只需要在`{}`里写样式,前面不需要任何选择器。如果某些编辑器对`{}`发出警告,看着不爽,可以这样写`this {}`,this表示第一个div 138 | 139 | 像这样子 140 | 141 | a.html 142 | 143 | ```html 144 | 151 |
152 |

Hello JTaro Module

153 |
154 | ``` 155 | 156 | a.js 157 | 158 | ```js 159 | import a from './a.html' 160 | document.body.innerHTML = a 161 | ``` 162 | 163 | 运行结果 164 | 165 | ```html 166 | 167 | 168 | 174 | 175 | 176 |
177 |

Hello JTaro Module

178 |
179 | 180 | 181 | ``` 182 | 183 | ### 处理css 184 | 185 | 直接将css文件的内容以style标签的形式在head创建,不会额外加任何标记 186 | 187 | >tips:创建局部样式请在html文件里使用style,创建全局样式,请使用css文件 188 | 189 | ## 注意事项 190 | 191 | - 目前只在chrome浏览器通过测试,而且将来也不太可能会去兼容其它浏览器。是的,没看错,对非chrome浏览器不做兼容。上线部署的时候将会移除几乎所有JTaro Module的代码,因此,只需要保证在chrome浏览器上开发不出问题就够了 192 | - 所有import的路径都是相对当前文件的,除非使用`rollup-plugin-paths`插件,JTaro Module会自动根据当前文件查找目标文件 193 | - a.js引入b.js,b.js引入a.js这类循环引入不会重复加载,但代码可能不会按预期的那样执行 194 | - import/export必须独立成行,即同一行不能出现两个import/export 195 | - import的文件必须加后缀,目前只支持js/html/css三种后缀文件 196 | - 入口文件不应该有export 197 | - 除以下5种import、5种export语法外的ES6模块语法都不会被解释到,例:不支持`export * from '../abc.js'` 198 | 199 | ## 支持import的5种语法 200 | 201 | > __注意:__`import`语句必须单独成行 202 | 203 | ```js 204 | // 1、花括号变量 205 | import { a } from './**.js' 206 | import { a, b, c } from './**.js' 207 | 208 | // 2、花括号别名 209 | import { a as apple } from './**.js' 210 | import { a as apple, b as banana } from './**.js' 211 | 212 | // 3、默认值赋给变量,相当于`import { default as a } from './**.js'` 213 | import a from './**.js' 214 | 215 | // 4、只引入并运行(适用于非ES6模块且有在window域暴露变量的库) 216 | import './**.js' 217 | 218 | // 5、获取所有值并赋给变量 219 | import * as a from './**.js' 220 | ``` 221 | 222 | ## 支持export的5种语法 223 | 224 | ```js 225 | // 1、var声明(请不要使用let/const声明,ES5不认) 226 | export var a = 'xxx' 227 | 228 | // 2、花括号变量 229 | var a = 1, b = 2, c = 3 230 | export { a, b, c } 231 | 232 | // 3、花括号别名 233 | export { a as apple, b as banana } 234 | 235 | // 4、导出函数 236 | export function a () { 237 | ... 238 | } 239 | 240 | // 5、导出默认值 241 | export default { a: 1 } 242 | ``` 243 | 244 | ## rollup-plugin-jtaro-module 245 | 246 | [rollup-plugin-jtaro-module](https://github.com/chjtx/rollup-plugin-jtaro-module) Rollup的JTaro Module插件,使Rollup支持引入html和css,上线打包时使用 247 | 248 | | 选项 | 默认值 | 说明 | 249 | |:----:|:----:|:----| 250 | | root | 当前工作目录 | 批定站点根目录,填入相对于工程目录的路径,让JTaro Module能正确处理文件路径 | 251 | 252 | ## 使用rollup的插件 253 | 254 | - 与 JTaro Module 源码`server.js`同一目录创建`jtaro.module.config.js`文件 255 | - 或者在开启服务时指定配置文件`node server.js --config=./jtaro.other.config.js` 256 | - `--config`选项后面跟的路径是相对`server.js`的,请用`./`或`../`开头 257 | - 配置该文件后,即可使用rollup的插件对文件进行处理,如使用`rollup-plugin-paths`进行别名修改,`rollup-plugin-babel`进行ES6语法转换等 258 | 259 | 目前已测试通过的rollup插件: 260 | 261 | - [rollup-plugin-paths](https://github.com/chjtx/rollup-plugin-paths) 可在不同目录层级下使用相同变量的路径 262 | - [rollup-plugin-babel](https://github.com/rollup/rollup-plugin-babel) 将ES6语法转换成ES5 263 | 264 | ``` 265 | npm i -D rollup-plugin-paths rollup-plugin-babel babel-preset-es2015 266 | ``` 267 | 268 | ```js 269 | // jtaro.module.config.js 270 | var alias = require('rollup-plugin-paths') 271 | var babel = require('rollup-plugin-babel') 272 | 273 | module.exports = { 274 | website: '../', // 站点目录,以server.js所在路径为基准 275 | entry: '../demos/main.js', // 入口文件,以server.js所在路径为基准 276 | plugins: [alias({ 277 | jquery: './vendors/jquery-2.2.3.min.js' // 以入口文件所在路径为基准 278 | }, babel({ 279 | include: '**/a.js', //该路径相对于entry(入口文件) 280 | 'presets': [ 281 | [ 282 | 'es2015', 283 | { 284 | 'modules': false 285 | } 286 | ] 287 | ] 288 | })] 289 | } 290 | ``` 291 | 292 | **注意** 293 | 294 | - 若要使用`rollup-plugin-babel`必须安装`babel-preset-es2015` 295 | - 强烈建议配置babel的`include`选项,否则每个js都会被编译,非常慢 296 | 297 | ## 参考 298 | 299 | - [ECMAScript 6 入门 - 阮一峰](http://es6.ruanyifeng.com/#docs/module) 300 | - [用NodeJS打造你的静态文件服务器](https://cnodejs.org/topic/4f16442ccae1f4aa27001071) 301 | - [Rollup.js官网](http://rollupjs.org/) 302 | 303 | ## 后语 304 | 305 | JTaro Module只能用于解决js/html/css的模块化,与webpack相比,简直是弱到爆。JTaro Module之所以存在,是因为webpack太过于强大,以至新手根本无法接近,随便抛一个错误足可让我等渣渣通宵达旦。JTaro Module每个文件都与真实文件对应,所有浏览器可捕捉的错误都显而易见,也许错误行号与原文件对不上,`ctrl/cmd + f`搜索一下就很轻易搜到错误源头。webpack是把牛刀,JTaro Module只是用来削水果的,合不合用就要使用者们自己度量了。 306 | 307 | 那么为什么要造轮子? 308 | 309 | - 新人入项,总要安装一大堆脚本工具,npm安装则网络受限,cnpm安装则依赖缺失 310 | - 公司预算约束,不可能给每位开发者提供mac设备,在3000元windows机上运行webpack等开发环境备受挑战 311 | - webpack学习成本较高,出现问题处理成本更高,并非新手所能驾驭 312 | - 经webpack处理过的脚本,并不能很直观的反映出是哪段业务代码报的错误,增加开发成本 313 | - .vue文件将html、js、css合在一起适合编写单个组件,对于业务逻辑较多的文件应将html、css和js分离 314 | - 工具应该用于解放劳动力,而不应该因维护工具而适得其反 315 | 316 | ## License 317 | 318 | MIT 319 | 320 | -------------------------------------------------------------------------------- /src/parse.js: -------------------------------------------------------------------------------- 1 | /*! JTaro-Module parse.js v0.3.0 ~ (c) 2017-2018 Author:BarZu Git:https://github.com/chjtx/JTaro-Module/ */ 2 | /** 3 | * JTaro Module 4 | * 将含以下规则的import/export解释成ES5可执行代码 5 | * import 6 | * 1) import { a, b, c } from './util.js' 7 | * 2) import { abc as a } from './util.js' 8 | * 3) import a from './util.js' 9 | * 4) import './util.js' 10 | * 5) import * as a from './util.js' 11 | * export 12 | * 1) export var a = 'xxx' 13 | * 2) export { a, b, c } 14 | * 3) export function a () {} 15 | * 4) export default a 16 | * 5) export { abc as a } 17 | */ 18 | 19 | function getImports (text) { 20 | return text.match(/^[\t ]*import\s+.*/mg) 21 | } 22 | 23 | function getExports (text) { 24 | return text.match(/([\t ]*export +[^{\n\r]+)|([\t ]*export +\{[^}]+\})/g) 25 | } 26 | 27 | function removeComment (text) { 28 | return text.replace(/\/\*[\s\S]*?\*\//g, '') 29 | } 30 | 31 | function resolvePath (p, d) { 32 | d = d.substr(0, d.lastIndexOf('/')) 33 | p = p.replace(/(\.\.\/)|(\.\/)/g, function (match, up) { 34 | if (up) { 35 | d = d.substr(0, d.lastIndexOf('/')) 36 | } 37 | return '' 38 | }) 39 | return (d + '/' + p).replace(/\\/g, '/') 40 | } 41 | 42 | function parseImport (arr, name, plugins) { 43 | var newArr = [] 44 | var regNormal = /import +['"](.+)['"]/ 45 | var regBracket = /import +\{([^}]+)\} +from +['"](.+)['"]/ 46 | var regDefault = /import +([^ {}]+) +from +['"](.+)['"]/ 47 | var regAll = /import +\* +as +([^ ]+) +from +['"](.+)['"]/ 48 | var varArr = [] 49 | var temp 50 | var variables 51 | 52 | // 参照 https://github.com/rollup/rollup/wiki/Plugins#creating-plugins 的resolveId描述 53 | function resolveId (a) { 54 | var b = null 55 | for (var i = 0, l = plugins.resIds.length; i < l; i++) { 56 | b = plugins.resIds[i](a, plugins.id) 57 | // 只要有一个resolveId函数匹配立即终止 58 | if (b) break 59 | } 60 | // 如果b有值,返回从当前文件到指定文件的相对路径 61 | // return b ? getRelativePath(plugins.id, b) : a 62 | return (b || a).replace(/\\/g, '/') 63 | } 64 | 65 | for (var i = 0, l = arr.length; i < l; i++) { 66 | let path = '' 67 | // import '**.js' 68 | if (regNormal.test(arr[i])) { 69 | path = 'JTaroLoader.import(\'' + resolveId(regNormal.exec(arr[i])[1]) + '\', g)' 70 | } 71 | // import { ... } from '**.js' 72 | if (regBracket.test(arr[i])) { 73 | temp = regBracket.exec(arr[i]) 74 | variables = temp[1].trim().split(/ *, */) 75 | variables.forEach(i => { 76 | var a = i.split(/ +\bas\b +/) 77 | varArr.push({ 78 | alias: a[1] || a[0], 79 | name: resolvePath(resolveId(temp[2]), name), 80 | variable: '.' + a[0] 81 | }) 82 | }) 83 | path = 'JTaroLoader.import(\'' + resolveId(temp[2]) + '\', g)' 84 | } 85 | // import default from '**.js' 86 | if (regDefault.test(arr[i])) { 87 | temp = regDefault.exec(arr[i]) 88 | varArr.push({ 89 | alias: temp[1], 90 | name: resolvePath(resolveId(temp[2]), name), 91 | variable: '.default' 92 | }) 93 | path = 'JTaroLoader.import(\'' + resolveId(temp[2]) + '\', g)' 94 | } 95 | // import * as a from '**.js' 96 | if (regAll.test(arr[i])) { 97 | temp = regAll.exec(arr[i]) 98 | varArr.push({ 99 | alias: temp[1], 100 | name: resolvePath(resolveId(temp[2]), name), 101 | variable: '' 102 | }) 103 | path = 'JTaroLoader.import(\'' + resolveId(temp[2]) + '\', g)' 104 | } 105 | if (path === '') { 106 | arr[i] = '' 107 | } else { 108 | newArr.push(path) 109 | } 110 | } 111 | return { 112 | imps: newArr, 113 | exps: varArr 114 | } 115 | } 116 | 117 | function joinImports (exps) { 118 | if (!exps.length) { 119 | return '' 120 | } 121 | var s = ', [' 122 | exps.forEach((item, index) => { 123 | s += 'JTaroModules[\'' + item.name + '\']' + item.variable 124 | if (index !== exps.length - 1) { 125 | s += ', ' 126 | } 127 | }) 128 | return s + ']' 129 | } 130 | 131 | function joinVariables (exps) { 132 | var s = '' 133 | exps.forEach((item, index) => { 134 | s += item.alias 135 | if (index !== exps.length - 1) { 136 | s += ', ' 137 | } 138 | }) 139 | return s 140 | } 141 | 142 | function mixHeader (loaders, name) { 143 | return '(function (f) {JTaroAssets[\'' + name + '\'] = 1;' + 144 | 'var g = function () { f.apply(null' + joinImports(loaders.exps) + ')};' + 145 | loaders.imps.join(';') + 146 | '})(function (' + joinVariables(loaders.exps) + ') {\n' 147 | } 148 | 149 | function nodeImport (a, f, h) { // (imps, file, header) 150 | var t 151 | for (var i = 0, l = a.length; i < l; i++) { 152 | t = i === l - 1 ? h : '\n' 153 | f = f.replace(new RegExp(a[i].replace('*', '\\*') + '(\n|\r\n|$)'), '/* ' + a[i] + ' */' + t) 154 | } 155 | return f 156 | } 157 | 158 | function parseBracket (v, name) { 159 | var a = v.split(/ *, */) 160 | var str = '' 161 | a.forEach((item, index) => { 162 | var b = item.split(/ *\bas\b */) 163 | if (b[1]) { 164 | str += 'JTaroModules[\'' + name + '\'].' + b[1] + ' = ' + b[0] 165 | } else { 166 | str += 'JTaroModules[\'' + name + '\'].' + b[0] + ' = ' + b[0] 167 | } 168 | if (index !== a.length - 1) { 169 | str += '\n' 170 | } 171 | }) 172 | return str 173 | } 174 | 175 | function parseExport (e, name) { 176 | var regDefault = /^[\t ]*export +default/ 177 | var regFunction = /^[\t ]*export +function +([^ ]+)/ 178 | var regVar = /^[\t ]*export +var +([^ ]+)/ 179 | var regBracket = /^[\t ]*export +\{([^}]+)\}/ 180 | var variable 181 | 182 | // export default ... 183 | if (regDefault.test(e)) { 184 | return e.replace(regDefault, 'JTaroModules[\'' + name + '\'].default =') 185 | } 186 | // export function ... 187 | if (regFunction.test(e)) { 188 | return e.replace(regFunction, 'JTaroModules[\'' + name + '\'].' + regFunction.exec(e)[1] + ' = function') 189 | } 190 | // export var ... 191 | if (regVar.test(e)) { 192 | return e.replace(regVar, 'JTaroModules[\'' + name + '\'].' + regVar.exec(e)[1]) 193 | } 194 | // export { ... } 195 | if (regBracket.test(e)) { 196 | variable = regBracket.exec(e)[1].trim() 197 | return e.replace(regBracket, parseBracket(variable, name)) 198 | } 199 | 200 | return e 201 | } 202 | 203 | function getExportMaps (exps, name) { 204 | var maps = [] 205 | exps.forEach(i => { 206 | maps.push({ 207 | source: i, 208 | replace: parseExport(i, name) 209 | }) 210 | }) 211 | return maps 212 | } 213 | 214 | function takePlugins (c) { 215 | var p = c.plugins || [] 216 | var resIds = [] 217 | var trsFms = [] 218 | var opts = [] 219 | p.forEach(function (i) { 220 | if (typeof i.resolveId === 'function') resIds.push(i.resolveId) 221 | if (typeof i.transform === 'function') trsFms.push(i.transform) 222 | if (typeof i.options === 'function') opts.push(i.options) 223 | }) 224 | // 先执行一轮rollup的options 225 | opts.forEach(function (o) { 226 | o(c) 227 | }) 228 | return { 229 | id: c.id, 230 | entry: c.entry, 231 | resIds: resIds, 232 | trsFms: trsFms 233 | } 234 | } 235 | 236 | // 执行rollup插件的transform函数 237 | function doTransform (f, p) { 238 | var t 239 | for (var i = 0, l = p.trsFms.length; i < l; i++) { 240 | t = p.trsFms[i](f, p.id) 241 | if (t && t.code) f = t.code 242 | } 243 | return f 244 | } 245 | 246 | module.exports = function (file, name, config) { 247 | var plgs = takePlugins(config) 248 | 249 | // 去掉多行注释的副本 250 | var copy = removeComment(file) 251 | // 提取import 252 | var imps = getImports(copy) 253 | 254 | var loaders = [] 255 | var header = '' 256 | var exportMaps 257 | 258 | if (imps) { 259 | // 转换成JTaroLoader.import 260 | loaders = parseImport(imps, name, plgs) 261 | // 头部 262 | header = mixHeader(loaders, name) 263 | // 注释已转换的import 264 | file = nodeImport(imps, file, header) + '\n})' 265 | } 266 | 267 | // 提取export 268 | var exps = getExports(copy) 269 | 270 | if (exps) { 271 | // 只有export没有import也需要使用闭包 272 | if (!imps) { 273 | file = '!function(){' + file + '\n}()' 274 | } 275 | 276 | exportMaps = getExportMaps(exps, name) 277 | exportMaps.forEach((item, index) => { 278 | if (index === 0) { 279 | file = file.replace(item.source, 'JTaroModules[\'' + name + '\'] = {};' + item.replace) 280 | } else { 281 | file = file.replace(item.source, item.replace) 282 | } 283 | }) 284 | } 285 | 286 | return doTransform(file, plgs) 287 | } 288 | -------------------------------------------------------------------------------- /src/client.js: -------------------------------------------------------------------------------- 1 | /*! JTaro-Module client.js v0.3.3 ~ (c) 2017-2018 Author:BarZu Git:https://github.com/chjtx/JTaro-Module/ */ 2 | /* global io */ 3 | /** 4 | * 保证先执行依赖文件的实现思路 5 | * 1、引入资源时,创建依赖树,节点主要包含的内容有{from, path, callback} 6 | * 2、根据子节点的from和父节点的path,将子节点push进所有依赖该子节点的父节点 7 | * 3、资源onload时根据JTaroAssets判断该资源是否有依赖,没有则将该节点从所有有引入该节点的父节点移除,并执行回调,有则略过 8 | * 4、从父节点移除后判断父节点的children是否为0,是则表示依赖已全部加载,将该父节点从它的所有父节点移除,即重复第3步直到children不为0 9 | */ 10 | (function () { 11 | var assets = [] 12 | var loader 13 | var loadCompleted = {} 14 | window.JTaroModules = {} 15 | window.JTaroAssets = {} 16 | 17 | // 如果该脚本没引入其它模块,立即执行回调 18 | function execScript (child) { 19 | if (!window.JTaroAssets[child.src]) { 20 | loadCompleted[child.src] = 1 21 | removeFromFather(child, assets) 22 | } 23 | } 24 | 25 | // 如果遍历assets都不存在该path的元素,则该脚本为最上层脚本 26 | function isTheTopLevel (p) { 27 | var a = [] 28 | var b = [] 29 | var i = 0 30 | a = a.concat(assets) 31 | while (a.length) { 32 | b = [] 33 | for (i = 0; i < a.length; i++) { 34 | if (a[i].path === p) return false 35 | if (a[i].children) b = b.concat(a[i].children) 36 | } 37 | a = b 38 | } 39 | return true 40 | } 41 | 42 | // 推进父级 43 | function pushIntoFather (child, fathers) { 44 | var hasThisChild 45 | var i 46 | var l 47 | var k 48 | for (i = 0, l = fathers.length; i < l; i++) { 49 | if (!fathers[i].children) fathers[i].children = [] 50 | // 匹配来源 51 | if (fathers[i].path === child.from) { 52 | hasThisChild = false 53 | for (k = 0; k < fathers[i].children.length; k++) { 54 | if (fathers[i].children[k].path === child.path) { 55 | hasThisChild = true 56 | break 57 | } 58 | } 59 | if (!hasThisChild) { 60 | fathers[i].children.push(child) 61 | } 62 | } 63 | if (fathers[i].children && fathers[i].children.length > 0) { 64 | pushIntoFather(child, fathers[i].children) 65 | if (fathers[i].path === child.path) { 66 | // 子树已存在该树中 67 | findChildFrom(child.path, fathers[i].children, child.path) 68 | } 69 | } 70 | } 71 | } 72 | 73 | // 查找是否循环引用 74 | function findChildFrom (p, children, childPath) { 75 | for (var i = 0, l = children.length; i < l; i++) { 76 | if (children[i].path === childPath) { 77 | console.error('Can\'t cyclic import! `' + p + ' -> ' + children[i].path + '`') 78 | return 79 | } else { 80 | if (children[i].children && children[i].children.length) { 81 | findChildFrom(p + ' -> ' + children[i].path, children[i].children, childPath) 82 | } 83 | } 84 | } 85 | } 86 | 87 | // 从父级移除 88 | function removeFromFather (child, fathers) { 89 | fathers.forEach(function (f) { 90 | if (f.children.length === 0) return 91 | var cb 92 | for (var i = 0, l = f.children.length; i < l; i++) { 93 | if (f.children[i].path === child.path) { 94 | cb = f.children.splice(i, 1)[0] 95 | break 96 | } 97 | } 98 | 99 | if (f.children.length === 0) { 100 | if (cb) { 101 | cb.callback() 102 | loadCompleted[f.src] = 1 103 | setTimeout(function () { 104 | removeFromFather(f, assets) 105 | }, 0) 106 | } 107 | } else { 108 | removeFromFather(child, f.children) 109 | } 110 | }) 111 | } 112 | 113 | loader = { 114 | // 路径处理 115 | path: { 116 | dirname (p) { 117 | return p.substr(0, p.lastIndexOf('/')) 118 | }, 119 | resolve (p) { 120 | var currentScript = (document.currentScript && document.currentScript.src) || document.baseURI.split('?')[0].split('#')[0] 121 | var d = this.dirname(currentScript) 122 | var path 123 | 124 | // 支持http/https请求 125 | if (/^http/.test(p)) { 126 | path = p 127 | 128 | // 相对路径请求 129 | } else { 130 | p = p.replace(/(\.\.\/)|(\.\/)/g, function (match, up) { 131 | if (up) { 132 | d = d.substr(0, d.lastIndexOf('/')) 133 | } 134 | return '' 135 | }) 136 | path = (d + '/' + p) 137 | } 138 | return { 139 | from: currentScript, 140 | src: path.replace(window.location.origin, ''), 141 | path: path 142 | } 143 | } 144 | }, 145 | // 判断脚本是否存在 146 | isExist (path) { 147 | var exist 148 | var scripts = document.getElementsByTagName('script') 149 | for (var i = 0, l = scripts.length; i < l; i++) { 150 | if (scripts[i].src === path) { 151 | exist = scripts[i] 152 | break 153 | } 154 | } 155 | return exist 156 | }, 157 | importJs (result) { 158 | var me = this 159 | var script = me.isExist(result.path) 160 | if (!script) { 161 | script = document.createElement('script') 162 | script.src = result.src 163 | script.addEventListener('load', function () { 164 | execScript(result) 165 | }) 166 | script.onerror = function (e) { 167 | console.error('`JTaroLoader.import(\'' + result.src + '\', g)` load fail from ' + result.from) 168 | } 169 | setTimeout(function () { 170 | // 防止多次引入同一模块 171 | var s = me.isExist(result.path) 172 | if (!s) { 173 | document.head.appendChild(script) 174 | } 175 | }, 0) 176 | } 177 | }, 178 | // 将路径转换成id 179 | path2id (path) { 180 | return path.substr(0, path.lastIndexOf('.')).replace(/\//g, '_') 181 | }, 182 | importHtml (result) { 183 | var me = this 184 | window.fetch(result.src).then((res) => { 185 | return res.text() 186 | }).then(data => { 187 | var reg = /