最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 教你搭建一个自己的脚手架(二)

    正文概述 掘金(BlackNiuNiu)   2020-12-02   392

    教你搭建一个自己的脚手架(二)

    【写在前面】

    在上一篇《教你搭建一个自己的脚手架(一)》中,我们介绍了一些脚手架的公共配置。完成了dev-server和对多文件格式的加载处理功能,这一节中,我们将继续完善配置,以期完成基础版的所有功能。
    完整的code在这里Github 点进去。与此同时,我们再回顾一下我们的目标。

    基础版标准版
    dev-server
    处理html/JS/VUE/CSS/LESS/SASS/IMG/JPG等各种文件
    路由配置
    mock数据
    代理接口
    打包分析
    单元测试
    编译构建
    dev-server
    处理html/JS/VUE/CSS/LESS/SASS/IMG/JPG等各种文件
    mock数据
    代理接口
    打包分析
    单元测试
    编译构建
    基本布局
    导航配置
    支持TS
    支持Markdown
    风格检查

    【看这里】

    在上一节里,webpack.config.base.js基本已经完成。我们需要针对开发和生产环境做一些差异化的配置。正如最开始设计的那样,我们将开发环境需要的配置放置到webpack.config.dev.js中,生产环境需要的配置放置到webpack.config.prod.js中。
    对比我们的设计目标,不难发现mock数据、接口代理和打包分析是开发场景中特有的需求,在生产环境中不需要。因此,我们先搞定研发场景。

    mock数据
    数据mock其实原理比较简单,即拦截请求,加载用户事先配置好的mock数据,作为response返回即可。
    社区中常见的方法可以分为两类,一类是直接在client的层面直接集成拦截钩子,比如axios有对应的axios-mock-adapter作为插件可以mock数据。另一类,是在server中间件的层面拦截请求。两种方式的区别在于,第一种方式并未真正的发出ajax请求,即在网络控制台中看不到network的记录;第二种方式发出了ajax请求,但请求在本地server被拦截了,请求并未真正从本机发出,但network面板中有记录存在。这里,我选择了第二种方式,原因显而易见,因为我们希望从chrome/firefox等用户代理的network面板中看到请求记录,方便我们调试和排查问题。
    通过查询webpack-dev-server的github,发现其实现是基于express的。express的中间件机制,允许我们注册自己的逻辑到调用链中。又因为并未看到有特别好的社区插件,我决定自己实现一个。
    首先,它最后的使用方式应该是很简单的, 比如类似这样子:

    /**
     * @file mock/api/user.js
     * @description mock api for user model
     * @author nimingdexiaohai(nimingdexiaohai@163.com)
     */
    module.exports = {
        queryUser: {
            url: /\/user\/\d$/,
            method: 'get',
            status: 200,
            response: {
                success: true,
                message: 'get user info success',
                data: {
                    id: 1,
                    name: 'John'
                }
            }
        },
        listUsers: {
            url: /\/user$/,
            method: 'get',
            status: 200,
            response: {
                success: true,
                message: 'list user info success',
                data: {
                    users: [
                        { id: 1, name: 'John' },
                        { id: 2, name: 'Sharon' }
                    ]
                }
            }
        }
    };
    

    样例中给了两条mock配置,一个是获取某个特定用户的信息,另一个是获取所有的用户列表。它的字段语义还是很明确的:

    // 需要拦截的路由path,支持字符串和正则匹配
    url: string/reg
    // 需要拦截的方法类型
    method: get/post/put/delete/option
    // 响应的状态码
    status: HTTP_STATUS_CODE
    // 响应的结构体,支持对象结构体和方法动态生成数据
    response: Object/Function
    

    下面,我们实现对路由的拦截和响应数据的加载功能。它应该提供一个单例模式的实例,应该有对外暴露的方法,可以设置状态、注册配置、加载钩子等。我们先来看下最后的实现,然后一一解读一下:

    /**
     * @file mock/lib.js
     * @description export mock
     * @author nimingdexiaohai(nimingdexiaohai@163.com)
     */
    const _ = require('lodash');
    
    const Mock = {
        on: true,
        rules: [],
    };
    
    Mock.Use = () => {
        Mock.on = true;
    };
    
    Mock.Restore = () => {
        Mock.on = false;
    };
    
    Mock.Reset = () => {
        Mock.on = false;
        Mock.rules = [];
    };
    
    Mock.Register = (c) => {
        c['on'] = c.hasOwnProperty('on') ? c['on'] : true;
        Mock.rules.push(c);
    };
    
    Mock.findRule = (path, method) => {
        const result = {willMock: false, rule: null};
        if (Mock.on) {
            for (let r of Mock.rules) {
                if (r.on
                    && r.method.toUpperCase() === method
                    && ((_.isString(r.url) && r.url === path) || (_.isRegExp(r.url) && r.url.test(path)))
                ) {
                    result.willMock = true;
                    result.rule = r;
                    break;
                }
            }
        }
        return result;
    };
    
    Mock.LoadMock = (req, res, next, app, server, compiler) => {
        if (req && req.path && req.method) {
            const {willMock, rule} = Mock.findRule(req.path, req.method);
            if (willMock) {
                console.log(`[${req.method.toUpperCase()}]${req.path} mocked by mocker...`);
                if (_.isFunction(rule.response)) {
                    res.status(rule.status).send(rule.response(req, res));
                }
                else {
                    res.status(rule.status).send(rule.response);
                }
                return;
            }
        }
        next();
    };
    
    module.exports = Mock;
    

    我们定义了一个Mock对象,它的属性包括了字段Mock.onMock.rules,分别用来管控mock的生效状态和路由拦截配置。同时暴露了多个方法支持对内部状态进行修改,其中Mock.useMock.RestoreMock.Reset是对生效状态的修改,Mock.RegisterMock.LoadMock是注册路由拦截规则和加载mock主逻辑的接口。导出的方法使用大驼峰命名方式,内部方法采用小驼峰命名方式。
    Mock.LoadMock中传入请求和响应对象,对请求的方法、路由进行判断,一旦命中用户配置的规则,则加载配置的响应数据返回给用户代理,并直接return,阻断后续的调用链。否则执行next(), 继续下一个中间件逻辑。这里的response支持配置成一个方法,动态的生成数据或者远程加载数据等。
    到这里,mock数据的主逻辑就有了。那这个中间件到底怎样注册呢?我们通过查询文档,发现devServer.before中暴露了接口,可以让我们自己定义的逻辑先于所有其它中间件执行,所以我们把拦截逻辑放到这里就可以了。

    // webpack.config.dev.js
    const MockUp = require('../mock/index');
    devServer: {
            before: function(app, server, compiler) {
                MockUp.registerAll();
                server.use(function(req, res, next) {
                    MockUp.loadMock(req, res, next);
                });
            },
        }
    

    devServer.before中首先执行了MockUp.registerAll方法,这个方法并未出现在我们对Mock的定义中。其实这里是为了更好的解耦应用和库逻辑,也为了使用插件的时候更方便,又封装了一下Mock导出的方法。它的具体实现如下:

    /**
     * @file mock/index.js
     * @description mock api entry
     * @author nimingdexiaohai(nimingdexiaohai@163.com)
     */
    const Mock = require('./lib');
    const User = require('./api/user');
    
    const apiModels = [User];
    module.exports = {
        registerAll: () => {
            apiModels.forEach(model => {
                Object.keys(model).forEach(key => {
                    Mock.Register(model[key]);
                });
            });
        },
        loadMock: (req, res, next, app, server, compiler) => {
            Mock.LoadMock(req, res, next, app, server, compiler);
        }
    };
    

    到这里,mock的整体脉络就比较清晰了。我们定义按实体model对象作为一个模块来划分mock的规则配置是一种好的实践,因此mock文件夹下有了这样的组织方式: 教你搭建一个自己的脚手架(二) 当然,你还可以根据自己的业务需求,在model下面再进行拆分。比api/user/common.jsapi/user/vip.js,一切都是可以调整的。

    接口代理
    接口的代理作为一个常规的研发需求,已经有很多优秀的社区开源插件可以支持。比较知名的有http-proxy-middleware,可以直接拿来使用。同样因为webpack-dev-server是基于express实现的原因,它可以用与mock同样的方式注册到devServer的中间件调用链中。
    除此之外,devServer.proxy默认集成了对外暴露的代理接口。我们可以直接使用,不再引入第三方插件,相关文档可以参考这里。它的使用方式,类似这样:

    /**
     * @file proxy.js
     *
     * @author nimingdexiaohai(nimingdexiaohai@163.com)
     * @see https://webpack.js.org/configuration/dev-server/#devserverproxy
     * @see https://github.com/chimurai/http-proxy-middleware 
     */
    module.exports = {
        '/api': {
            target: 'http://localhost:3000',
            changeOrigin: true,
            pathRewrite: { '^/api': '/path' }
        }
    };
    

    打包分析
    在很多工作场景中,产品发布到了线上,会出现产出包size过大,导致首次加载较慢的问题出现。这种状态的改善,一般依赖于多个方面:启用Gzip压缩、使用CDN、拆分产出包。我们这里只关注第三个部分。
    拆分代码是一个相对完整的体系范畴,它包含了多种拆分的原则和操作方法。这一点,我们在生产环境的配置中再详细解读。线上的包体积是要严格控制的,但优化的路径确是提前设计好的,因此我们需要一个工具来分析一下打包出来的产出体积和结构。
    webpack-bundle-analyzer是一个比较知名的开源实现,它以可视化的方式,展示了各种产出包的Size和依赖关系。

    // webpack.config.dev.js
    const webpack = require('webpack');
    const { merge } = require('webpack-merge');
    const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
    const baseWebpackConfig = require('./webpack.config.base.js');
    
    module.exports = merge(baseWebpackConfig, {
        plugins: [
            new BundleAnalyzerPlugin({
                // 'disabled' will close analyser
                analyzerMode: 'server',
            }),
        ],
    });
    

    这个插件会随着启动dev脚本的时候,同步启动一个单独的server,并展示各种构建包和依赖关系。鼠标悬浮到某个方块,可以看到它的size信息。 教你搭建一个自己的脚手架(二)

    其它配置
    除了上面的配置,开发环境还需要有热加载、sourceMap等功能。完整的开发环境配置如下:

    /**
     * @file webpack.config.dev.js
     * @author nimingdexiaohai(nimingdexiaohai@163.com)
     */
    const webpack = require('webpack');
    const MockUp = require('../mock/index');
    const proxy = require('../src/common/proxy');
    const { merge } = require('webpack-merge');
    const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
    const baseWebpackConfig = require('./webpack.config.base.js');
    
    module.exports = merge(baseWebpackConfig, {
        mode: 'development',
        devtool: 'eval-cheap-source-map',
        stats: 'minimal',
        plugins: [
            new BundleAnalyzerPlugin({
                // 'disabled' will close analyser
                analyzerMode: 'server',
            }),
            new webpack.HotModuleReplacementPlugin(),
        ],
        devServer: {
            hot: true,
            open: true,
            before: function(app, server, compiler) {
                MockUp.registerAll();
                server.use(function(req, res, next) {
                    MockUp.loadMock(req, res, next);
                });
            },
            // proxy: proxy
        }
    });
    

    webpack.HotModuleReplacementPlugin是启用热加载功能,是webpack^5最新的写法。devtool是配置了sourceMap的生成方式,根据不同的取值,其打包构建的速度也不同,最后生成的map信息粒度也不同。webpack^5中的该字段取值,也发生了一些变化,具体可以参考devtool官方文档。
    sourceMap的作用原理在另一篇文章《UNDERSCORE.js 源码解析(一)》中,做过详细的说明,这里不再赘述了。

    生产环境的配置较为简单,出了产出构建包,再加一个代码拆分的配置就可以了。但为了配合在开发环节就设计好拆分原则,我们将拆分代码的配置抽取到了webapck.config.base.js中。

    /**
     * @file webpack.config.base.js
     * @author nimingdexiaohai(nimingdexiaohai@163.com)
     */
    const path = require('path');
    const {CleanWebpackPlugin} = require('clean-webpack-plugin');
    const MiniCssExtractPlugin = require('mini-css-extract-plugin');
    
    const config = require('./config');
    
    module.exports = {
        ...
        plugins: [
            new CleanWebpackPlugin(),
            new MiniCssExtractPlugin({
                filename: utils.genFilePathWithName('[name].css')
            }),
        ],
        optimization: {
            splitChunks: {
                cacheGroups: {
                    vendor: {
                        test: /[\\/]node_modules[\\/]/,
                        name: 'vendor',
                        chunks: 'initial',
                        priority: -10
                    },
                    default: {
                        minChunks: 2,
                        priority: -20,
                        reuseExistingChunk: true
                    }
                }
            }
        }
    };
    

    optimization.splitChunks.cacheGroups中定义了两个分组, priority字段定义了优先级,表示当资源同时满足两个分组的匹配时,优先使用哪个规则对资源进行处理。这里是将node_modules中的三方依赖都打包到了vendor.js中,initial表示只抽取与入口文件依赖共享的三方文件。default是另外一个默认分组,表示未命中vendor分组的都使用这条规则。minChunks表示当依赖同时被2个以上的资源文件引用时,就将其抽取成一个单独的包。yarn dev启动脚手架,并访问http://localhost:8080/webpack-dev-server就可以看到拆分后的包结构了: 教你搭建一个自己的脚手架(二)

    当然,这里面包含了一些devServer生成的支持运行时调试的文件,但不影响我们关注和评估自己的配置所产生的拆分效果。关于代码拆分的其它配置项使用,可以参看codeSplitting专题和split-chunks-plugin插件。所以,生产环境的配置就只剩下一个mode字段的配置,用来标识其环境场景:

    /**
     * @file webpack.config.prod.js
     * @author nimingdexiaohai(nimingdexiaohai@163.com)
     */
    const config = require('./config');
    const webpack = require('webpack');
    const { merge } = require('webpack-merge');
    const baseWebpackConfig = require('./webpack.config.base.js');
    
    module.exports = merge(baseWebpackConfig, {
        mode: 'production',
        stats: 'minimal',
    });
    

    测试
    完成了开发环境和生产环境的基础配置。还需要集成一个测试框架来支持我们对一些核心逻辑配套单元测试。

    "scripts": {
        "dev": "cross-env NODE_ENV=development webpack serve --config ./build/webpack.config.dev.js",
        "build": "cross-env NODE_ENV=production webpack --config ./build/webpack.config.prod.js --progress --color",
        "test": "cross-env NODE_ENV=test karma start test/karma.conf.js"
      },
    

    我们选用了karma + mocha + karma-spec-reporter构建测试框架。其中,karma是一个用于跑测试case的工具。mocha是一个可以同时支持运行在Node环境和Browser环境的测试框架,它。karma-spec-reporter是用来生成测试结果报告的。
    在集成过程中,发现karma-webpack还未支持webpack^5。因此测试集成并未完全完成,又因为不愿意妥协降级到webpack4,所以决定等待webpack^5的支持后,再做进一步的动作。支持计划见github.com/ryanclark/k…

    【总结】

    到这里,脚手架的基础版就基本完成了。欢迎交流沟通~

    微信同步更新: 教你搭建一个自己的脚手架(二)


    起源地下载网 » 教你搭建一个自己的脚手架(二)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元