浏览器运行时编译es6+react+jsx+antd+router+redux+dva和vue+vue单文件

开发背景,主要有时候开发前端的一些小例子,用到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>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值