开发背景,主要有时候开发前端的一些小例子,用到react或VUE的单文件组件等,这些或其它的需要解析转换后,才能在浏览器运行。但直接使用nodejs环境有点太麻烦了,特别是安装一堆node_modules,那使用systemjs感觉不够灵活,而且大部分cdn打包都是umd格式,所以选择用requirejs来运行,利用babel转换和解析ast语法,
其实现在项目越来越大,编译越来越慢,包括使用微前端拆分项目。其实运行时编译我觉得也可以作为解决编译慢的手段,像webpack也是支持打包成systemjs格式,再利systemjs实现运行时编译
React 的解析
相对简单一点,因为babel本身提供了babel-plugin-react插件,要做的就是利用babel识别import哪些是本地的,本地的就需要把es-next语法转换到es5
VUE的解析
首先利用我下面写的babel-require,fetch读取入口文件,然后利用babel识别import添加预处理VUE的预处理,然后转换.vue后缀文件的源码用vue-template-compiler,转换完成后会有{styles:[],script:{},templet:{}}的结果,利用babel解析ExportDefaultDeclaration 声明把template解析成函数或直接模板变量添加render到js块中,styles可以添加到style页面,需要作 用域的话或less可以用less.js转换,用正则识别根元素添加一个作 用域 属性,把css包起来,只对这个单组件起作用
想体验效果,直接复制下面一个HTML文件代码和JS文件代码,直接就可以运行了,如果是单独的文件会用到fetch读取源码解析,所以要在服务器运行,因为我配置的都是CDN文件
配置步聚:
首页在html里面引入requirejs和我写的requirebabel
下面是requirebabel.js文件
然后定义相关模块,定义一个init模块就会自动运行,也可以引用es6js独立文件,
如果引用的话就直接在import 'babel:es6',你得配置require.config,如果你不配置也可以用相对路径
在html script定义的模块,会自动生成在源码调试面板里面可以看到,方便调试打段点
最终效果如下:
下面的代码就是requirebabel/react.js文件,requirejs.js自己在网上下
function transformCode(source,sourceFileName,moduleId) {
return new Promise((resolve, reject) => {
try {
let code = Babel.transform(source, {
presets: [['env', {
modules: "amd",
}], ['react']],
plugins:[function(babel){
var t=babel.types;
return {
visitor:{
CallExpression(path){
let callee=path.get('callee');
if(callee.isImport()){
let args=path.get('arguments');
if(args.length>0&&args[0].node.value.indexOf('./')!==-1){
args[0].replaceWith(t.StringLiteral("babel!"+ args[0].node.value))
}
}
},
ImportDeclaration(ast){
if(ast.node.source.value.indexOf('./')!=-1||ast.node.source.value.indexOf('../')!==-1){
//ast.node.source.value='babel!'+ast.node.source.value;
ast.get('source').replaceWith(t.StringLiteral('babel!'+ast.node.source.value))
}
}
}
}
}],
sourceFileName: sourceFileName,
// inputSourceMap:true,
sourceMap: "inline",//'inline',
sourceType:"module",
moduleId:moduleId,
// moduleIds:moduleId
// plugins:[]
}).code;
resolve(code);
} catch (e) {
reject();
}
})
}
function addSourceCode(source){
transformCode(source).then((source)=>{
eval(source);
})
}
define('css', () => {
return {
load(name, req, onload, config) {
if (config.isBuild) {
onload(null);
} else {
var url = req.toUrl(name);
var link = document.createElement('link');
link.href = url;
link.rel = "stylesheet";
document.getElementsByTagName('head')[0].appendChild(link);
onload(null);
}
}
}
})
define('transformCode',['babelStandalone'],(Babel)=>{
return function transformCodeHandler(source,sourceFileName,moduleId) {
return transformCode(source,sourceFileName,moduleId)
}
})
define('babel', ['transformCode', 'module'], (transformCode, _module) => {
var elA = document.createElement('a');
function getUrl(path) {
elA.href = path;
return elA.href;
}
function StringArrayBuffer(str, cb) {
var b = new Blob([str], { type: "text/javascript" });
var f = new FileReader();
f.onload = function (e) {
cb(e.target.result);
}
f.readAsArrayBuffer(b);
}
var pluginOptions = _module.config();
var defaultFileExtension = pluginOptions.fileExtension || '.js';
return {
// normalize: function (name, normalize){
// return name+".js"
// },
load(name, req, onload, config) {
if (config.isBuild) {
onload(null);
} else {
var fileExtension=defaultFileExtension;
if(name.indexOf('.js')!==-1){
name=name.slice(0,-3);
}
var sourceFileName = name + fileExtension;
var url = req.toUrl(sourceFileName);
fetch(url).then(res => res.text()).then((source) => {
var elScript = document.createElement('script');
elScript.type = "text/javascript"
elScript.async = true;
elScript.addEventListener('load', function () {
onload();
})
transformCode(source,sourceFileName).then((source) => {
//elScript.src = blobUtil.createObjectURL(blobUtil.binaryStringToBlob(source, 'text/javascript'));
// elScript.innerHTML=source;
// document.body.appendChild(elScript);
//eval(source+'\n//# sourceURL=' + url);
onload.fromText(source + '\n//# sourceURL=' + url)
})
})
// var link=document.createElement('link');
// link.href=url;
// link.rel="stylesheet";
// document.getElementsByTagName('head')[0].appendChild(link);
// onload(null);
}
}
}
})
requirejs.config({
//context:"react",
init:true,
baseUrl: "/",// location.href.substr(0, location.href.lastIndexOf('/')),
paths: {
"routerUtil-es6": "/scripts/applicationTools/mvvm/react/utils/render-routes",
moment: "https://cdn.jsdelivr.net/npm/moment@2.27.0/moment",
"react-router-dom": "https://cdn.jsdelivr.net/npm/react-router-dom@5.2.0/umd/react-router-dom",
// "react-router-dom": "https://cdn.jsdelivr.net/npm/react-router-dom@4.2.0/umd/react-router-dom",
blobUtil: "https://cdn.jsdelivr.net/npm/blob-util@2.0.2/dist/blob-util",
babelStandalone: "https://cdn.jsdelivr.net/npm/@babel/standalone@7.11.2/babel.min",
react: "https://cdn.jsdelivr.net/npm/react@16.13.1/umd/react.development",
'react-dom': "https://cdn.jsdelivr.net/npm/react-dom@16.13.1/umd/react-dom.development",
"antd": "https://cdn.jsdelivr.net/npm/antd@4.5.2/dist/antd",
"antdcss": "https://cdn.jsdelivr.net/npm/antd@4.5.2/dist/antd.min.css",
"pro-layout-css": "https://cdn.jsdelivr.net/npm/@ant-design/pro-layout@6.3.0/dist/prolayout.min.css",
"pro-layout": "https://cdn.jsdelivr.net/npm/@ant-design/pro-layout@6.3.0/dist/prolayout",
"react-rudex":"https://cdn.jsdelivr.net/npm/react-redux@7.2.1/dist/react-redux",
'redux':"https://cdn.jsdelivr.net/npm/redux@4.0.5/dist/redux",
"immutable":"https://cdn.jsdelivr.net/npm/immutable@3.8.2/dist/immutable",
"dva":"https://cdn.jsdelivr.net/npm/dva@2.4.1/dist/dva",
'immer':'https://cdn.jsdelivr.net/npm/immer@7.0.7/dist/immer.umd.development',
"mock":"https://cdn.jsdelivr.net/npm/mockjs@1.1.0/dist/mock-min"
},
shim: {
"antd": ["css!antdcss"],
react: {
exports: "React",
},
"pro-layout": {
exports: "ProLayot",
deps: ['react', "css!pro-layout-css"]
}
},
deps: ['babelStandalone','blobUtil','transformCode','react','react-dom','antd'],
callback(Babel,blobUtil,transformCode,React,ReactDOM,antd) {
window.Babel=Babel;
window.React = React;
window.antd=antd;
window.ReactDOM=ReactDOM;
var scripts=document.querySelectorAll('script[type="text/babel"]');
var newPaths={},len=scripts.length,isCanInit=false;
var complete=()=>{
if(--len<=0){
requirejs.config({
paths:newPaths
})
isCanInit&&require(['init']);
}
}
scripts.forEach((elScript)=>{
var name=elScript.getAttribute('data-module');
var source=elScript.innerHTML;
if(!isCanInit&&name=='init'){
isCanInit=true;
}
transformCode(source,name+'.js',name).then(source=>{
//newPaths[name]=blobUtil.createObjectURL(blobUtil.binaryStringToBlob(source, 'text/javascript'));
eval(source);
complete();
})
})
if(requirejs.s.contexts._.config.init){
if(!document.querySelector('app')){
var wrapper=document.createElement('div');
wrapper.id='app';
wrapper.className='app';
document.body.appendChild(wrapper);
}
loadStyle(`
html,body,.app{
height:100%;
}
`)
require(['babel!/scripts/private/packages/webReact/src/index']);
}
}
});
function loadStyle(css){
var style=document.createElement('style');
try{
style.appendChild(document.createTextNode(css));
}catch(e){
style.styleSheet.cssText=css;
}
document.getElementsByTagName('head')[0].appendChild(style);
}
define('routerUtil', ['babel!routerUtil-es6'], function (r) {
return r;
});
//资源加载完成
requirejs.onResourceLoad=(context,map, resLoadMaps)=>{
}
//requirejs.s.contexts._.makeModuleMap=
var oldRequireLoad=requirejs.load;
requirejs.load=function(context, moduleName, url){
return oldRequireLoad(context,moduleName,url)
}
requirejs.onError = function (err) {
console.log(err.message);
if (err.requireType === 'timeout') {
console.log('modules: ' + err.requireModules);
}
throw err;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
html,body,#app,.wrapper{
height: 100%;
}
</style>
</head>
<body>
<div id="app"></div>
<script src="/scripts/applicationTools/modules/require.js/2.3.6/require.js"></script>
<script src="/scripts/private/requireBabel/react.js"></script>
<!--应用状态管理-->
<script type="text/babel" data-module="store">
import dva from 'dva';
//import Immutable from 'immutable';
import produce from 'immer';
const app = dva();
app.use({
_handleActions(handlers, defaultState) {
return function (state = defaultState, action) {
var type = action.type;
var ret = produce(state, function (draft) {
var handler = handlers[type];
if (handler) {
var compatiableRet = handler(draft, action);
if (compatiableRet !== undefined) {
return compatiableRet;
}
}
});
return ret === undefined ? {} : ret;
};
}
})
app.model({
namespace: "global",
state: {
count: 0
},
reducers: {
add(state, action) {
state.count++;
return state;
}
}
})
export default app;
</script>
<!--母版页-->
<script type="text/babel" data-module="basic-layout">
import React, { useCallback } from 'react';
import ProLayot from 'pro-layout';
import { Link } from 'react-router-dom';
export default (props) => {
let menuItemRender = useCallback((menuDataItem, defaultDom) => {
if (menuDataItem.path) {
return <Link to={menuDataItem.path}>{defaultDom}</Link>
}
return defaultDom;
}, []);
return <ProLayot
route={props.route}
location={{
pathname: "/"
}}
title="DX"
layout="side"
menuItemRender={menuItemRender}>
<div className="wrapper">
{props.children}
</div>
</ProLayot>
}
</script>
<!--首页-->
<script type="text/babel" data-module="view-index">
import React, { useCallback } from 'react';
import { connect } from 'dva';
import { Button } from 'antd';
let Index = (props) => {
const onAdd = useCallback(() => {
props.dispatch({ type: "global/add" })
}, [])
return <div>
{props.count}
<Button onClick={onAdd}>add</Button>
</div>
}
export default connect(({global}) =>{
return global;
})(Index);
</script>
<!--初始化入口-->
<script type="text/babel" data-module="init">
import React from 'react';
import BasicLayout from 'basic-layout';
import { renderRoutes2 } from 'routerUtil';
import {Router} from 'react-router-dom';
import app from 'store';
import ViewIndex from 'view-index';
let routes = [{
path: "/",
component: BasicLayout,
// exact:false,
routes: [{
path: "/",
redirect: "/index"
}, {
path: "/index",
menu: {
name: "首页"
},
component:ViewIndex
}, {
path: "/index2",
menu: {
name: "首页2"
},
component: ()=>{
return <div>222</div>
}
}]
}];
app.router(({ app,history,...extraProps }) => <Router {...extraProps} history={history}>{renderRoutes2(routes)}</Router>)
app.start('#app')
//ReactDOM.render(<App></App>, document.getElementById('app'))
</script>
</body>
</html>