sourceMap是个啥

为啥用sourceMap这几天在搞前端错误日志,做过线上发布的都知道,我们发布到生产环境的代码,一般都有如下步骤: 压缩混淆,减小体积多个文件合并,减少HTTP请求数通过编译或者转译,将其他语言编译成JavaScript这三个步骤,都使得实际运行的代码不同于开发代码,不管是 debug 还是捕获线上的报错,都会变得困难重重。 解决这个问题的方法,就是使用sourceMap。 啥是sourceMap简单说,sourceMap就是一个文件,里面储存着位置信息。 仔细点说,这个文件里保存的,是转换后代码的位置,和对应的转换前的位置。 有了它,出错的时候,通过断点工具可以直接显示原始代码,而不是转换后的代码。 sourceMap长啥样通过webpack等工具,我们可以使用 sourceMap,这里不细说配置方法,可以看这里 sourceMap是一个map文件,与源码在同一个目录下。 在压缩代码的最后一行,会有这样的一个引用: //# sourceMappingURL=app.js.map指向的就是我们的map文件。 sourceMap的格式如下: { version : 3, //SourceMap的版本,目前为3 sources: ["foo.js", "bar.js"], //转换前的文件,该项是一个数组,表示可能存在多个文件合并 names: ["src", "maps", "are", "fun"], //转换前的所有变量名和属性名 mappings: "AACvB,gBAAgB,EAAE;AAClB;", //记录位置信息的字符串 file: "out.js", //转换后的文件名 sourcesContent: " \t// The module cache\n", //转换后的代码 sourceRoot : "" //转换前的文件所在的目录。如果与转换前的文件在同一目录,该项为空}其他的都很好解释,我们详细说一下mappings属性。 mappings以"AACvB,gBAAgB,EAAE;AAClB;"为例: 每个分号对应转换后源码的一行;每个逗号对应转换后源码的一个位置;AACvB代表该位置转换前的源码位置,以VLQ编码表示;位置对应的原理位置关系的保存经历了诸多步骤和优化,这个不详细说了,想看的可以看这里,我们只说最后的结果。 在每个位置中: 第一位,表示这个位置在【转换后代码】的第几列。第二位,表示这个位置属于【sources属性】中的哪一个文件。第三位,表示这个位置属于【转换前代码】的第几行。第四位,表示这个位置属于【转换前代码】的第几列。第五位,表示这个位置属于【names属性】的哪一个变量。举例假设现在有a.js,内容为feel the force,处理后为b.js,内容为the force feel 以the为例,它在输出中的位置是(0,0),a.js是sources的第1个(这里只是举例),输入中的位置是(0,5),the是names的第2个(这里只是举例)。 那么映射关系为:0 1 0 5 2 最后将 01052 表示为 Base64 VLQ 即可。 ...

August 28, 2019 · 1 min · jiezi

深入解读-Knative-Eventing-07-版本新特性

前言Knative Eventing 0.7 版本已经于 6 月 26 号正式发布。本次发布主要围绕重构 Channel 特性展开。本篇文章重点解读了这些特性,并且以此展望一下 Knative Eventing 后续版本的发展。 新特性重构 Channel作为 Eventing v0.7 版本最大的特性, 重构了 Channel 的设计:为每个 Channel 单独创建了CRD资源。在 Eventing v0.6 版本中, Channel 是通过 provisioner 模式实现的。以 kafka Channel 为例: apiVersion: eventing.knative.dev/v1alpha1 kind: Channel metadata: name: my-kafka-channel spec: provisioner: apiVersion: eventing.knative.dev/v1alpha1 kind: ClusterChannelProvisioner name: kafka这里是通过指定名称为 kafka 的 ClusterChannelProvisioner。这样的实现方式存在以下问题: Channel 中只通过一个 provisioner 字段就设置了包含的所有属性。每一个Channel Controller都会监听到所有的资源,再进行过滤。Event Source中的实现方式更符合规范,即每个Source 单独的CRD和Controller,值得借鉴。针对这些之前存在的不合理的设计, 在Eventing v0.7版本中,为每个Channel 单独创建了CRD资源,改造涉及如下: InMemoryChannel CRD 替换 in-memory ClusterChannelProvisionerKafkaChannel CRD 替换 kafka ClusterChannelProvisionerNatssChannel CRD 替换 natss ClusterChannelProvisioner改造后的 kafka Channel 示例如下: ...

July 1, 2019 · 1 min · jiezi

线上出bug了别怕这么定位

摘要: Source Map还是很神奇的。 原文:线上出bug了?别怕,这么定位!公众号:前端小苑Fundebug经授权转载并修改,版权归原作者所有。 工作中,生产环境代码是编译后代码,搜集到报错信息的行和列无法在源码中对应,很多时候只能靠“经验”去猜,本文针对这种情况,开发了一个npm命令行小工具,帮助快速定位报错的源码位置,提升效率。 由于现在构建工具盛行,前端部署的代码都是经过编译,压缩后的,于是乎,SoueceMap就扮演了一个十分重要的角色,用来作为源代码和编译代码之间的映射,方便定位问题。 测试SourceMap功能首先全局安装reverse-sourcemap npm install --global reverse-sourcemap选择编译后的代码进行测试,下面是vue项目编译生成的代码。 在命令行执行命令,将main.js反编译回源码,并输出到sourcecode目录下。 reverse-sourcemap -v dist/main.a8ebc11c3f03786d8e3b.js.map -o sourcecode 上面是执行命令输出的sourcecode目录,生成的目录结构和源码目录一致,打开一个文件,和源码做下对比: 可以看出,反编译出的代码无论目录结构还是具体代码都和源码一致。 生产环境代码报错如何定位源码位置如果使用了Fundebug的Source Map功能的话,则可以很方便的定位出错位置: 如果没有使用监控工具的话,生产环境的代码,经过压缩、编译,很不利于Debug。针对这个问题,需要准备一份生产环境代码的map文件,为了方便,可以在项目的package.json增加debug命令用来生成map文件。这条命令除了开启sourcemap,其他的具体webpack配置和生产环境配置相同。 "scripts": { "start": "vue-cli-service serve --mode dev", "stage": "vue-cli-service build --mode staging", "online": "vue-cli-service build", "debug": "vue-cli-service build --mode debug" }有了map文件,通过SourceMap提供的API就可以定位到源码的位置。下面是实现的核心代码。 // Get file contentconst sourceMap = require('source-map');const readFile = function (filePath) { return new Promise(function (resolve, reject) { fs.readFile(filePath, {encoding:'utf-8'}, function(error, data) { if (error) { console.log(error) return reject(error); } resolve(JSON.parse(data)); }); });};// Find the source locationasync function searchSource(filePath, line, column) { const rawSourceMap = await readFile(filePath) const consumer = await new sourceMap.SourceMapConsumer(rawSourceMap); const res = consumer.originalPositionFor({ 'line' : line, 'column' : column }); consumer.destroy() return res}最重要的就是使用SourceMap提供的 originalPositionFor API。 SourceMapConsumer.prototype.originalPositionFor(generatedPosition) ...

June 1, 2019 · 1 min · jiezi

如何优雅地查看 JS 错误堆栈?

摘要: 堆栈是Debug的关键。原文:如何优雅地查看 JS 错误堆栈?作者:小芭乐Fundebug经授权转载,版权归原作者所有。在前端,我们经常会通过 window.onerror 事件来捕获未处理的异常。假设捕获了一个异常,上报的堆栈是这个:TypeError: Cannot read property ‘module’ of undefined at Object.exec (https://my.cdn.com/dest/app.efe91e855d7432e402545e7d6c25d2d9.js:16:29828) at HTMLLIElement.<anonymous> (https://my.cdn.com/dest/app.efe91e855d7432e402545e7d6c25d2d9.js:25:6409) at HTMLDivElement.dispatch (https://my.cdn.com/dest/vendor.eb28ded1876760b8e90973c9f4813a2c.js:1:248887) at HTMLDivElement.y.handle (https://my.cdn.com/dest/vendor.eb28ded1876760b8e90973c9f4813a2c.js:1:245631)这个堆栈,你看得出问题来吗?我们发布到 CDN 的脚本文件,普遍是经过 UglifyJS 压缩的,所以堆栈可读性相当的差。假如有下面的一个堆栈查看工具,又如何?眼尖的同学,一眼就能找到问题。这里的 p[e] 出现了可能为 undefined 的情况。这样一个工具,大大提高了问题定位的效率。好,这里不卖瓜,我们来看下这当中的实现原理。一步步来说的话:拿到原始堆栈字符串,使用 error-stack-parser解析为堆栈帧,每个堆栈帧包含三个最重要的字段:url - 源码的 URL 地址line - 堆栈位置行号col - 堆栈位置列号对于 url,我们可以用于加载源码内容,得到 sourcesource 使用 UglifyJs 反向美化成多行的代码 prettysource,并且同时生成 sourcemap堆栈帧中的 line 和 col 通过 sourcemap 反查,得到美化后对应的 prettyline 和 prettycol将 prettysource、prettyline、prettycol 给到 Monaco Editor 渲染,就可以得到上述截图的效果说那么多,不如贴代码是吧:var result = UglifyJS.minify(source, { output: { beautify: true }, sourceMap: { filename: ‘pretty.js’, url: ‘pretty.js.map’ }});var code = result.code;var rawSourceMap = JSON.parse(result.map);var consumerPromise = new sourceMap.SourceMapConsumer(rawSourceMap);resolve( consumerPromise.then(function(consumer) { return { code: code, sourceMapConsumer: consumer } }));上面就是使用 UglifyJs 对压缩代码进行反向美化的核心代码。下面给出 SourceMap 的使用源码:var code = result.code;var consumer = result.sourceMapConsumer;var position = consumer.generatedPositionFor({ source: ‘0’, line: lineNumber, column: columnNumber});parent.postMessage({ event: ‘js-prettify-callback’, payload: { hash: payload.hash, result: ‘success’, prettySource: code, prettyLineNumber: position.line, prettyColumnNumber: position.column + 1 }}, sourceOrigin);完整源码有兴趣的读者也可以下下来把玩把玩:js-loader.html.zip源码只包含堆栈解析的实现,UI 的实现不在本文的讨论之内,用 React 随便画一画就好了。 ...

March 8, 2019 · 1 min · jiezi

webpack4系列教程(九):开发环境和生产环境

构建开发环境如果你一直跟随我前面的博文,那么你对webpack的基础知识已经有比较深刻的理解了。之前,我们一直执行着:npm run build来打包编译输出我们的代码,本文我们来看看如何构建一个开发环境,来使我们的开发变得方便些。1.1 webpack-dev-serverwebpack-dev-server是一个简单的小型的web服务器,并且能够实时重载,配置也很简单,首先安装:npm install –save-dev webpack-dev-server配置webpack.config.js:devServer: { port: 8080, // 端口号 host: ‘0.0.0.0’, // 主机名,设为该值可通过IP访问 overlay: { errors: true // 错误提示 } }在package.json中添加命令: “dev”: “cross-env NODE_ENV=development webpack-dev-server –config config/webpack.config.js"执行:npm run dev可见我们的服务已经跑起来了:1.2 source-map在webpack打包源码时,我们会很难找到错误的出现位置,比如将源文件 sum.js、minus.js打包到bundle.js中,其中一个源文件出现了错误,仅仅会追踪到bundle.js中,这对我们来说并不理想。因此为了更加便捷的找到错误的原始位置,JavaScript为我们提供了 source-map的功能,将编译后的代码映射回原始源代码。如果一个错误来自于 sum.js,source map 就会明确的告诉你。 我们来测试一下,在sum.js中输出一个错误:// ES Mudule 规范export default function (a, b) { console.error(’this is test’) // 输出错误 return a + b}在没有devtool配置的情况下 npm run dev,会发现错误提示的行数并不准确,原因是我们的代码是被编译过的然后在webpack.config.js中加入配置: devtool: ‘inline-source-map’, // 加入devtool配置当配置文件改动时需要重新执行 npm run dev:错误提示行数以及源码映射都是正确的。devtool的取值有很多,大家可根据需要自行配置1.3 模块热替换模块热替换(Hot Module Replacement)是 webpack 提供的最有用的功能之一。它允许在运行时更新各种模块,而无需进行完全刷新。使用非常简单,在webpack.config.js中引入webpack: const webpack = require(‘webpack’)在plugins数组中添加: new webpack.NamedModulesPlugin(), new webpack.HotModuleReplacementPlugin()给devServer中的hot属性设为true:devServer: { port: 8080, // 端口号 host: ‘0.0.0.0’, // 主机名,设为该值可通过IP访问 overlay: { errors: true // 错误提示 }, hot: true }这样我们修改代码的时候就可以局部刷新模块而不是刷新整个页面了。2.构建生产环境开发环境(development)和生产环境(production)的构建目标差异很大。在开发环境中,我们需要具有强大的、具有实时重新加载(live reloading)或热模块替换(hot module replacement)能力的 source map 和 localhost server。而在生产环境中,我们的目标则转向于关注更小的 bundle,更轻量的 source map,以及更优化的资源,以改善加载时间。由于要遵循逻辑分离,我们通常建议为每个环境编写彼此独立的 webpack 配置。虽然,以上我们将生产环境和开发环境做了略微区分,但是,请注意,我们还是会遵循不重复原则(Don’t repeat yourself - DRY),保留一个“通用”配置。为了将这些配置合并在一起,我们将使用一个名为 webpack-merge 的工具。通过“通用”配置,我们不必在环境特定的配置中重复代码。我们先从安装 webpack-merge 开始,用来合并webpack配置项:npm install –save-dev webpack-merge在config文件夹下创建 webpack.dev.js 和 webpack.build.js 并修改 webpack.config.js,将开发与生产环境的公共配置放在webpack.config.js中:const path = require(‘path’)const HtmlWebpackPlugin = require(‘html-webpack-plugin’)const MiniCssExtractPlugin = require(‘mini-css-extract-plugin’)const isDev = process.env.NODE_ENV === ‘development’const config = { entry: { main: path.join(__dirname, ‘../src/main.js’) }, output: { filename: ‘[name].bundle.js’, path: path.join(__dirname, ‘../dist’) }, module: { rules: [ { test: /.(vue|js|jsx)$/, loader: ’eslint-loader’, exclude: /node_modules/, enforce: ‘pre’ }, { test: /.js$/, loader: ‘babel-loader’, exclude: /node_modules/ }, { test: /.vue$/, loader: ‘vue-loader’, options: createVueLoaderOptions(isDev) }, { test: /.ejs$/, use: [’ejs-loader’] }, { test: /.css$/, use: [ isDev ? ‘vue-style-loader’ : MiniCssExtractPlugin.loader, { loader: ‘css-loader’, options: { importLoaders: 1 } }, ‘postcss-loader’ ] }, { test: /.less$/, use: [ isDev ? ‘vue-style-loader’ : MiniCssExtractPlugin.loader, ‘css-loader’, { loader: ‘postcss-loader’, options: { sourceMap: true } }, ’less-loader’ ] }, { test: /.(jpg|jpeg|png|gif|svg)$/, use: [ { loader: ‘url-loader’, options: { name: ‘[path][name]-[hash:5].[ext]’, limit: 1024 } } ] } ] }, plugins: [ new HtmlWebpackPlugin({ template: path.join(__dirname, ‘../index.html’), inject: true, minify: { removeComments: true } }) ]}module.exports = configwebpack.dev.jsconst merge = require(‘webpack-merge’)const common = require(’./webpack.config.js’)module.exports = merge(common, { mode: ‘development’, devtool: ‘inline-source-map’, devServer: { port: 8080, host: ‘0.0.0.0’, overlay: { errors: true }, historyApiFallback: { index: ‘/index.html’ } }})webpack.build.jsconst path = require(‘path’)const CleanWebpackPlugin = require(‘clean-webpack-plugin’)const MiniCssExtractPlugin = require(‘mini-css-extract-plugin’)const merge = require(‘webpack-merge’)const common = require(’./webpack.config.js’)module.exports = merge(common, { mode: ‘production’, optimization: { splitChunks: { chunks: ‘initial’, automaticNameDelimiter: ‘.’, cacheGroups: { commons: { name: ‘commons’, chunks: ‘initial’, minChunks: 2, priority: 3 }, vendors: { test: /[\/]node_modules[\/]/, priority: 1 } } }, runtimeChunk: { name: entrypoint => manifest.${entrypoint.name} } }, plugins: [ new MiniCssExtractPlugin({ filename: ‘[name].css’ }), new CleanWebpackPlugin( [‘dist’], { root: path.join(__dirname, ‘../’) } ) ]})修改package.json的命令:“dev”: “cross-env NODE_ENV=development webpack-dev-server –config config/webpack.dev.js”,“build”: “cross-env NODE_ENV=production webpack –config config/webpack.build.js –progress –inline –colors"现在分别执行 npm run dev 和 npm run build 就会得到你想要的了。本人才疏学浅,如有不当之处,欢迎批评指正

January 17, 2019 · 2 min · jiezi