首页
Search
1
解决visual studio code (vscode)安装时没有选择安装路径问题
138 阅读
2
Linux 下 Bash 脚本 bad interpreter 报错的解决方法
131 阅读
3
Arch Linux 下解决 KDE Plasma Discover 的 Unable to load applications 错误
107 阅读
4
如何在 Clash for Windows 上配置服务
77 阅读
5
Uniapp如何引入自定义样式文件
75 阅读
clash
服务器
javascript
全部
游戏资讯
登录
Search
加速器之家
累计撰写
1,061
篇文章
累计收到
0
条评论
首页
栏目
clash
服务器
javascript
全部
游戏资讯
页面
搜索到
624
篇与
的结果
2024-08-24
搭建 Gatsby 博客二:使用 GraphQL 管理资源
为什么用 GraphQL上一篇介绍了选择 Gatsby 的原因,其中提到了 Gatsby 使用 GraphQL 。大家可能会有疑惑,不是建静态博客么,怎么会有 GraphQL?难道还要部署服务器?其实这里 GraphQL 并不是作为服务器端部署,而是作为 Gatsby 在本地管理资源的一种方式。通过 GraphQL 统一管理实际上非常方便,因为作为一个数据库查询语言,它有非常完备的查询语句,与 JSON 相似的描述结构,再结合 Relay 的 Connections 方式处理集合,管理资源不再需要自行引入其它项目,大大减轻了维护难度。带魔法的 GraphQL这里也是 Gatsby 的第一个坑。在 Gatsby 中,根据 js 文件的位置不同,使用 GraphQL 有两种形式,且 Gatsby 对其做了魔法,在 src/pages 下的页面可以直接 export GraphQL 查询,在其它页面需要用 StaticQuery 组件或者 useStaticQuery hook。这里面查询语句虽然写的是字符串,但其实这些查询语句不会出现在最终的代码中,Gatsby 会先对其抽取。个人其实不太喜欢魔法,因为会增加初学者的理解难度。但不得不承认魔法确实很方便,就是用了魔法的项目应该在文档最显眼的地方说明一遍。快速上手 GraphQLGraphQL 结构跟最终数据很相似,基本语法也非常简单,看看官方文档即可。一个快速上手的方式是访问项目开发时(默认 http://localhost:8000)的 /___graphql 页面,通过 GraphiQL 编辑器右侧可以浏览所有能够查询的资源。另一个需要理解的是 Relay 的 Connections 概念,你会发现 Gatsby 里所有的数据集合都是以这种方式查询。推荐阅读 Apollo 团队分享的文章。对 Connections 细致的理解往往是实现分页等底层需求时才需要,而这些均有插件完成。一般使用时只需要知道集合里每个项目的数据在 edges.node 中,同时通过 GraphiQL 浏览其它可以使用的数据。如对于 Markdown 文章,相应插件提供了字数统计以及阅读时长等数据,均可通过 GraphQL 直接获取。Debug GraphQLGatsby 魔法带来的另外一个坑是 GraphQL 报错信息不全,可能会默默被吞掉,也可能无法定位到最终文件。我在修改 starter 时踩到一个坑是复制组件时忘了修改 static query 查询语句的名称,导致重名报错。避免错误最好方式是在 GraphiQL 编辑器中写好运行无误再复制到组件中。Remark 插件坑Gatsby 中处理 markdown 最常用也是默认的插件是 gatsby-transformer-remark。这个插件对 markdown 文件解析后会生成 MarkdownRemark GraphQL 节点,其中 front matters 数据也会被解析出来。同时 MarkdownRemark 的集合对应为 allMarkdownRemark connections。对于 connections 节点我们一般可以用 sort 和 filter 来筛选处理数据(可在 GraphiQL 编辑器中浏览),这里有一个坑便是如果要处理 front matters 数据,它们必须存在所有查询的 markdown 文件上并且具有相同的类型,插件才会生成相应的 fields,否则可能会抛出异常或者更糟糕的,默默失败了。避免方式同上,先在 GraphiQL 编辑器中运行一遍,看看筛选的结果是否正确。另外一种处理方式是在 /gatsby-node.js 中通过 onCreateNode 钩子,在生成 markdown 相关节点时手工处理,确保节点存在。这在实现草稿和上下篇的时候会用到,具体例子我会在后续文章中再写。
2024年08月24日
4 阅读
0 评论
0 点赞
2024-08-24
搭建 Gatsby 博客三:使用 Netlify CMS 管理文章
为什么选择 Netlify CMS搭建 Gatsby 博客其实不需要 CMS 都是可以的,编写 Markdown 然后 build 即可。但这么做还是略嫌不便,通过 CMS 一般可以在一个可视化的在线环境中编辑文章,然后一键即可发布。Gatsby 主流的两个 CMS 是 Contentful 和 Netlify CMS。对于 Contentful 来说,文章是放在 Contentful 的服务器上的,管理也是通过 Contentful 提供的工具。当然其质量还是不错的,喜欢的可以参照官方的教程搭建。Netlify CMS 是跟项目一起发布的,默认是在 /admin 页面下。文章也是存在源项目中,就是原来默认的 Markdown 文件。Netlify CMS 借助 Oauth 把写好的 Markdown 文件推送到项目源码的仓库上,再配合 Netlify 检测仓库变动自动构建发布。当然后者也不是必须的,可以换其它方式自动构建。Netlify CMS 的优点是开源免费,文章跟项目源码在一起,界面可以高度自定义,甚至可以自行扩充 React 组件,基本满足简单的博客编写需求。配置 Netlify CMS如果用官方的 starter 配置将会非常简单。此 starter 默认使用 Github 作为仓库,Netlify 作为自动构建服务器。配置 Widgets默认的 /static/admin/config.yml 已经配置好了大部分,如果对文章 Markdown 添加了自定义的 front matters 则需要再做些细调。Widgets 代表了在 CMS 中可输入的模块,官方为常见的类型都提供了默认的 widgets ,没有满足的也可以自定义。如我的博客中每篇文章都有一个 quote 域放些引用文字,那么在配置中添加上fields: - label: "Quote" name: "quote" widget: "object" fields: - {label: "Content", name: "content", widget: "text", default: "", required: false} - {label: "Author", name: "author", widget: "string", default: "", required: false} - {label: "Source", name: "source", widget: "string", default: "", required: false}如此即可在 CMS 中填写相关信息。配置预览CMS 中提供了文章预览界面,如果需要自定义只需修改 /src/cms/ 下相应的文件即可,就是简单的 React 组件。以上便是 Netlify CMS 最常用的配置,只需简单的修改博客现在就能跑起来了。接下来的文章我们会通过实现草稿模式和上下篇文章来深入理解 Gatsby 的机制。
2024年08月24日
7 阅读
0 评论
0 点赞
2024-08-24
搭建 Gatsby 博客五:实现草稿模式和上下篇
草稿模式草稿模式即可以将文章保存为草稿而不被渲染出来。方式是在 front matters 中设置一个 draft 布尔域,以此域作为渲染参考。坑这里有一个地方需要注意,前面文章提过,Markdown 插件需要所有文章中都有 draft 域且都是布尔类型才会生成相应的 GraphQL 查询。如果是新的博客这个问题不大,如果是迁移过来的,有两个解决方式,第一个是手动写个脚本给文章都补上域,另一个是利用 Gatsby 的 Node APIs 在 fields 上生成特定域,鲁棒性更好些。自动生成域观察 Remark 插件生成的 GraphQL 类型,我们可以发现,front matters 都被放在 frontmatter 域中,而与之同级的有一个前面文章提到过的 fields 域,用来放自定义生成的数据。Gatsby 在生成 GraphQL 节点时提供了钩子 onCreateNode,我们利用这个钩子往 fields 中放自定义的数据。编辑 /gatsby-node.js,如果是用了 starter 的话这里很可能已经有其它的代码,已有的不需要动,添加我们需要的即可。exports.onCreateNode = ({ node, actions, getNode }) => { const { createNodeField } = actions if (node.internal.type === `MarkdownRemark`) { createNodeField({ node, name: 'draft', value: Boolean(node.frontmatter.draft) }) } }如此 fields 中就保证了会有 draft 这个域了。过滤草稿有了标记之后,在生成页面的地方我们就需要过滤草稿。首先是普通的文章页面生成,这个是在 createPages 钩子中,如果你的博客只有文章用到 Markdown 的话,可以在 GraphQL 查询中直接过滤,否则我们用前面文章的方法,先取所有 Markdown 文件再根据渲染的模板来分别处理各种类型的文章。注意我把模板域的名字换成了自己更习惯的 layout,原来的 starter 中应该叫 templateKey。修改其实也很简单,搜索所有文件替换关键字即可。options .filter( (_, i) => !( edges[i].node.frontmatter.layout === 'blog-post' && edges[i].node.fields.draft ) ) .forEach(option => createPage(option))我在主页中也列举了最近的几篇文章,这里也需要过滤草稿,可以直接在 GraphQL 中过滤。query IndexQuery { latestPosts: allMarkdownRemark( sort: { order: DESC, fields: [frontmatter___date] } filter: { fields: { draft: { ne: true } } frontmatter: { layout: { eq: "blog-post" } } } limit: 5 ) { edges { node { excerpt(pruneLength: 200) id fields { slug } frontmatter { title description layout date(formatString: "MMMM DD, YYYY") } } } } }其它地方同理。上下篇在文章页面中我们通常会加入上下篇来引导继续浏览。这里我们同样在 createPages 钩子中处理,但这回我们添加到 context 域中,这个域里的数据会作为 props 传到模板组件中。在 createPage 生成文章页面前添加处理代码计算上下篇:options .filter( (_, i) => edges[i].node.frontmatter.layout === 'blog-post' && !edges[i].node.fields.draft ) .forEach((option, i, blogPostOptions) => { option.context.prev = i === 0 ? null : { title: blogPostOptions[i - 1].title, path: blogPostOptions[i - 1].path } option.context.next = i === blogPostOptions.length - 1 ? null : { title: blogPostOptions[i + 1].title, path: blogPostOptions[i + 1].path } })然后在文章的 /src/templates/blog-post.js 组件里,接收 pageContext props,就可以使用上面传入的数据了。这是我的例子。通过实现这几个功能我们了解了 Gatsby 页面生成的方式以及其 Node APIs 的基本使用。Gatsby 的功能远不止这些,官方文档写得非常详细,需要实现其它功能建议先去看看有无现有的例子。本系列到这里暂告一段落,谢谢你的阅读,希望能对你搭建 Gatsby 博客有所帮助。
2024年08月24日
9 阅读
0 评论
0 点赞
2024-08-24
React 音频显示波形与区间循环播放
先上 Saladict 中实现的效果:以及源码例子:WaveSurfer首先对于音频区间选择的交互,我第一反应是要做成音频处理软件那样的效果:显示波形,然后可以直接在波形上选择区间。做 Waveform 算是小众需求,所以这方面的库不多,比较一番之后认为 WaveSurfer 这个开源项目相对靠谱,而且它可以通过插件支持区间选择。但是它一个纯 JS 的库,结合 React 我们需要做些简单的处理,主要是组件挂载时加载以及销毁时释放。export default class Waveform extends React.PureComponent { componentDidMount () { this.wavesurfer = WaveSurfer.create({ container: '#waveform-container', }) } componentWillUnmount () { if (this.wavesurfer) { this.wavesurfer.destroy() this.wavesurfer = null } } render () { return { } } }这里面就有一个坑,Chrome 在不久前改了政策,在页面刚加载发生用户交互之前 AudioContext 是处于一个 suspended 状态,即无法进行播放,需要监听 statechange 事件在状态变成 running 之后再调用 resume() 方法恢复。然而实测这个 statechange 事件发生的时机有点玄学,且通过 wavesurfer.backend.ac.resume() 之后不知为何还是不能加载出波形。所以最保险的方式是懒加载 WaveSurfer,在第一次播放的时候才初始化,这时几乎可以保证用户已经进行了交互。export default class Waveform extends React.PureComponent { initWaveSurfer = () => { this.wavesurfer = WaveSurfer.create({ container: '#waveform-container', }) } loadAudio = (src) => {if (this.wavesurfer) {this.reset()} else {this.initWaveSurfer()}this.wavesurfer.load(src)}reset = () => {if (this.wavesurfer) {this.wavesurfer.pause()this.wavesurfer.empty()}} componentDidMount () { // 监听交互事件// 回调 this.loadAudio(src) } componentWillUnmount () { if (this.wavesurfer) { this.wavesurfer.destroy() this.wavesurfer = null } } render () { return { } } }接下来处理播放和暂停,比较直接的方式是监听 Wavesurfer 的 play 和 pause 再来改变组件 state,然而考虑到后面需要做其它处理,而这两个方法在我们后面特殊处理循环的时候触发频率会比较高,所以就不监听而分开处理。export default class Waveform extends React.PureComponent { state = { isPlaying: false, loop: true } initWaveSurfer = () => { this.wavesurfer = WaveSurfer.create({ container: '#waveform-container', }) wavesurfer.on('ready', this.play) wavesurfer.on('finish', this.onPlayEnd) } play = () => { this.setState({ isPlaying: true }) if (this.wavesurfer) { this.wavesurfer.play() } } pause = () => { this.setState({ isPlaying: false }) if (this.wavesurfer) { this.wavesurfer.pause() } } togglePlay = () => { this.state.isPlaying ? this.pause() : this.play() } onPlayEnd = () => { this.state.loop ? this.play() : this.pause() } // ... }区间选择接下来就是实现区间选择。Wavesurfer 提供了 Regions plugin,我们需要针对交互做些调整。import RegionsPlugin from 'wavesurfer.js/dist/plugin/wavesurfer.regions.min.js' export default class Waveform extends React.PureComponent { state = { isPlaying: false, loop: true } initWaveSurfer = () => { this.wavesurfer = WaveSurfer.create({ container: '#waveform-container', plugins: [RegionsPlugin.create()] }) // 允许鼠标划选区间 wavesurfer.enableDragSelection({}) // 鼠标按下去那一刻触发 wavesurfer.on('region-created', region => { // Region 插件支持多个区间,我们只保留最新一个 this.removeRegion() this.region = region }) // 鼠标放开那一刻触发 wavesurfer.on('region-update-end', this.play) // 播放指示器刚出区间区域那一刻触发 wavesurfer.on('region-out', this.onPlayEnd) // 鼠标点任意波形区域时触发,改变指示器位置 wavesurfer.on('seek', () => { if (!this.isInRegion()) { // 用户点击了区间外,把区间移除掉 this.removeRegion() } }) wavesurfer.on('ready', this.play) wavesurfer.on('finish', this.onPlayEnd) } /** 检查当前指示器是否在区间中 */ isInRegion = (region = this.region) => { if (region && this.wavesurfer) { const curTime = this.wavesurfer.getCurrentTime() return curTime >= region.start && curTime { if (this.region) { this.region.remove() } this.region = null } play = () => { this.setState({ isPlaying: true }) if (this.region && !this.isInRegion()) { // 如果指示器不在区间中则重新从区间起点播放 this.wavesurfer.play(this.region.start) } else { // 否则继续播放 this.wavesurfer.play() } } // ... }注意 Regions 插件中其实提供了循环播放的方法,但是为了接下来更细粒度的控制,我们改为监听指示器移出区间右侧时的 region-out 事件,然后与 finish 事件用同样的方法处理。本文介绍了如何结合 React 和 Wavesurfer 显示音频波形,实现区间选择和循环播放。下篇文章中我将继续分享如何实现音频的加速和减速。
2024年08月24日
6 阅读
0 评论
0 点赞
2024-08-24
搭建 Karma+Mocha+Chai 测试 TypeScript 项目
为什么不用 JestJest 是一个非常简单易用的测试框架,它内置了测试中各种常用功能,API 简洁清晰,只需少量配置即可快速编写测试。但同时 Jest 内部造了很多轮子,比如模块路径解析就曾踩到坑;其使用 JSDOM 模拟浏览器环境来支持并行测试,大大加快了速度,但也带来了 JSDOM 的局限性与坑;最大的问题是不能方便地在真实浏览器中测试,这个缺陷多年一直没有得到官方重视,未来支持的可能性很小。所以,如果只需要简单地测试框架组件以及一些纯逻辑的功能,使用 Jest 无疑是最方便的;但如果涉及到 DOM 相关的一些测试,就不必费时间在 Jest 上折腾 puppeteer 了,直接上 Karma 全家桶反而更容易。为什么要自行搭建许多框架 CLI 和脚手架都可以自动配置好测试框架,为什么我们还要自行搭建?除去研究学习的原因,整个搭建起来其实也没有想象中的困难,每个工具各司其职给了我们很大的自由去挑选合适的组合。预设的方案一般很难符合每个项目的需要,像 Neutrino 脚手架就意识到这一点,Neutrino 9 虽然依然号称“零配置”,但其角色已从项目零配置转移到工具零配置上。了解工具的基本原理,即便是配置脚手架来也会更得心应手。出场角色介绍首先我们来了解一下将要出场的几个工具是干什么的: Karma:负责统筹整个测试流程,获取测试,在多个真实设备上运行,对结果进行分析处理。 Mocha:测试框架,提供 API 编写测试。 Chai:BDD / TDD 风格断言库,提供了语义化的方式编写断言。 Istanbul:跟踪代码运行路径,计算测试覆盖率。 Coveralls:测试覆盖率分析服务。 Travis CI:持续集成服务,每次提交 commit 即可触发测试。 配置 Karmanpm install -D karma要使用 Karma 只需要配置一个文件即可,理论上放哪里都行,方便起见我们在项目根目录新建 karma.config.js。module.exports = config => { config.set({ singleRun: !!process.env.CI, mime: { 'text/x-typescript': ['ts', 'tsx'] } }) } singleRun:在本地我们通常希望测试跑完之后 Karma 依然继续检测文件变化重跑,但在 CI 上则必须完成结束测试,否测 CI 会一直等待 Karma 直到超时。 mime: Karma 不认识 TypeScript 文件,我们得告诉它。 添加浏览器npm install -D karma-chrome-launcher karma-firefox-launcher根据你的需求添加即可。module.exports = config => { config.set({ singleRun: !!process.env.CI, browsers: process.env.CI ? ['Chrome', 'Firefox'] : ['Chrome'], mime: { 'text/x-typescript': ['ts', 'tsx'] } }) }添加 Mocha 和 Chainpm install -D mocha chai karam karma-mocha karma-chai @types/chai @types/mocha植入全局变量就不必反复 import。module.exports = config => { config.set({ singleRun: !!process.env.CI, browsers: process.env.CI ? ['Chrome', 'Firefox'] : ['Chrome'], frameworks: ['mocha', 'chai'], mime: { 'text/x-typescript': ['ts', 'tsx'] } }) }让 TypeScript 知道 Chai 全局我们的测试也将使用 TypeScript 编写,@types/Mocha 会自动添加全局类型,但 Chai 默认不会,所以我们为测试的 ts 新建一个配置 test/tscofig.json{ "extends": "../tsconfig", "compilerOptions": { "typeRoots": [ "../node_modules/@types", "./typings" ] }, "include": [ "./**/*.spec.tsx" ] }然后新建 test/typings/global/index.d.tsimport * as Chai from 'chai' declare global { interface Window { expect: Chai.ExpectStatic } var expect: Chai.ExpectStatic }使用 Karma Webpacknpm install -D webpack karma-webpack ts-loader @types/webpack-env我们将使用 karma-webpack 对 TypeScript 进行打包。module.exports = config => { config.set({ singleRun: !!process.env.CI, browsers: process.env.CI ? ['Chrome', 'Firefox'] : ['Chrome'], mime: { 'text/x-typescript': ['ts', 'tsx'] }, webpackMiddleware: { noInfo: true, stats: 'errors-only' }, webpack: { mode: 'development', entry: './src/index.ts', output: { filename: '[name].js' }, devtool: 'inline-source-map', module: { rules: [ { test: /\.tsx?$/, use: { loader: 'ts-loader', options: { configFile: 'test/tsconfig.json' } }, exclude: [path.join(__dirname, 'node_modules')] } ] }, resolve: { extensions: ['.tsx', '.ts', '.js', '.json'] } } }) }注意我们这里给 ts-loader 指向了测试的 tsconfig.json。为什么不用 Babel Typescript也许你听说过 @babel/preset-typescript,它确实也可以转换 Typescript,但也仅仅是转换,由于无法进行 type-check,生成的代码还是有些不一样。如果没有特殊的需求,用回原生的编译器显然坑更少。为什么不用 Rollup 打包如果你的项目是基于 Rollup 的,重用 Rollup 的配置来加载测试似乎是个更好的选择。但也要注意 Rollup 的生态跟 Webpack 相比还是有些差距,许多 Rollup 相关的插件都疏于维护,我之前就一直没法让 rollup-plugin-istanbul 正确定位到 TypeScript 的源码。添加测试入口使用 Webpack 之后我们就不需要 Karma 来匹配文件,而是交给 Webpack 处理,只留一个入口给 Karma 即可。module.exports = config => { config.set({ singleRun: !!process.env.CI, browsers: process.env.CI ? ['Chrome', 'Firefox'] : ['Chrome'], mime: { 'text/x-typescript': ['ts', 'tsx'] }, files: ['test/index.ts'], preprocessors: {'test/index.ts': ['webpack']}, webpackMiddleware: { noInfo: true, stats: 'errors-only' }, webpack: { mode: 'development', entry: './src/index.ts', output: { filename: '[name].js' }, devtool: 'inline-source-map', module: { rules: [ { test: /\.tsx?$/, use: { loader: 'ts-loader', options: { configFile: 'test/tsconfig.json' } }, exclude: [path.join(__dirname, 'node_modules')] } ] }, resolve: { extensions: ['.tsx', '.ts', '.js', '.json'] } } }) }同时新建 test/index.js 将所有测试以及所有需要计算覆盖率的源码都 require 进来。const tests = require.context('./', true, /\.spec\.tsx?$/) tests.keys().forEach(tests) const sources = require.context('../src/', true, /\.tsx?$/) sources.keys().forEach(sources)添加超萌的喵喵汇报器npm install -D karma-nyan-reporter接下来我们配置最后一步,汇报测试结果。这里不得不提这个萌萌的 karma-nyan-reporter如果不希望出现动画,只开启 renderOnRunCompleteOnly 即可。module.exports = config => { config.set({ singleRun: !!process.env.CI, browsers: process.env.CI ? ['Chrome', 'Firefox'] : ['Chrome'], mime: { 'text/x-typescript': ['ts', 'tsx'] }, files: ['test/index.ts'], preprocessors: { 'test/index.ts': ['webpack'] }, reporters: ['nyan'], webpackMiddleware: { noInfo: true, stats: 'errors-only' }, webpack: { mode: 'development', entry: './src/index.ts', output: { filename: '[name].js' }, devtool: 'inline-source-map', module: { rules: [ { test: /\.tsx?$/, use: { loader: 'ts-loader', options: { configFile: 'test/tsconfig.json' } }, exclude: [path.join(__dirname, 'node_modules')] } ] }, resolve: { extensions: ['.tsx', '.ts', '.js', '.json'] } }, nyanReporter: { renderOnRunCompleteOnly: process.env.CI } }) }添加 Istanbul 汇报覆盖率npm install -D istanbul-instrumenter-loader karma-coverage-istanbul-reporter整合 Istanbul 主要分两步,先在 Webpack 中配置 instrumenter 以植入计数,最后配置汇报器汇报结果。module.exports = config => { config.set({ singleRun: !!process.env.CI, browsers: process.env.CI ? ['Chrome', 'Firefox'] : ['Chrome'], mime: { 'text/x-typescript': ['ts', 'tsx'] }, files: ['test/index.ts'], preprocessors: { 'test/index.ts': ['webpack'] }, reporters: ['nyan', 'coverage-istanbul'], webpackMiddleware: { noInfo: true, stats: 'errors-only' }, webpack: { mode: 'development', entry: './src/index.ts', output: { filename: '[name].js' }, devtool: 'inline-source-map', module: { rules: [ { test: /\.tsx?$/, use: { loader: 'ts-loader', options: { configFile: 'test/tsconfig.json' } }, exclude: [path.join(__dirname, 'node_modules')] }, {test: /\.tsx?$/,include: [path.join(__dirname, 'src')],enforce: 'post',use: {loader: 'istanbul-instrumenter-loader',options: { esModules: true }}} ] }, resolve: { extensions: ['.tsx', '.ts', '.js', '.json'] } }, coverageIstanbulReporter: process.env.CI? {reports: ['lcovonly', 'text-summary'],dir: path.join(__dirname, 'coverage'),combineBrowserReports: true,fixWebpackSourcePaths: true}: {reports: ['html', 'lcovonly', 'text-summary'],dir: path.join(__dirname, 'coverage/%browser%/'),fixWebpackSourcePaths: true,'report-config': {html: { outdir: 'html' }}}, nyanReporter: { renderOnRunCompleteOnly: process.env.CI } }) }注意这里我们配置汇报器输出的类型, text-summary 主要方便我们在命令行中看到统计。 html 可以 coverage/ 目录下浏览各个源代码的覆盖情况。 lcovonly 主要为了给其它工具使用,比如接下来要配置的 Coveralls。 如果你的源码中有针对浏览器特性或坑的代码,那么每个浏览器测试出来的覆盖率可能会不一样,通过 combineBrowserReports 即可合并覆盖率。如果需要分开查看,给 dir 路径提供 /%browser%/ 会自动分开生成各个浏览器的结果。让 Istanbul 忽略部分源码出于各种原因,有时候我们希望让 Istanbul 忽略一部分难以测试的源码。可以在代码中加入特殊的注释如 /* istanbul ignore next */ 来实现,其文档列举了完整的例子。但无论如何,最好一并附上忽略的原因,以免给阅读源码的人(包括未来的自己)造成困扰。上传覆盖率到 Coverallsnpm install -D karma-coveralls接下来我们可以将覆盖率上传到各种服务中进行分析,这里我们以 Coveralls 为例。注意我们配置了只在 CI 上上传,因为我们前面配置了 Karma 在本地会常开着不停跑测试。module.exports = config => { config.set({ singleRun: !!process.env.CI, browsers: process.env.CI ? ['Chrome', 'Firefox'] : ['Chrome'], mime: { 'text/x-typescript': ['ts', 'tsx'] }, files: ['test/index.ts'], preprocessors: { 'test/index.ts': ['webpack'] }, reporters: process.env.CI? ['nyan', 'coverage-istanbul', 'coveralls']: ['nyan', 'coverage-istanbul'], webpackMiddleware: { noInfo: true, stats: 'errors-only' }, webpack: { mode: 'development', entry: './src/index.ts', output: { filename: '[name].js' }, devtool: 'inline-source-map', module: { rules: [ { test: /\.tsx?$/, use: { loader: 'ts-loader', options: { configFile: 'test/tsconfig.json' } }, exclude: [path.join(__dirname, 'node_modules')] }, { test: /\.tsx?$/, include: [path.join(__dirname, 'src')], enforce: 'post', use: { loader: 'istanbul-instrumenter-loader', options: { esModules: true } } } ] }, resolve: { extensions: ['.tsx', '.ts', '.js', '.json'] } }, coverageIstanbulReporter: process.env.CI ? { reports: ['lcovonly', 'text-summary'], dir: path.join(__dirname, 'coverage'), combineBrowserReports: true, fixWebpackSourcePaths: true } : { reports: ['html', 'lcovonly', 'text-summary'], dir: path.join(__dirname, 'coverage/%browser%/'), fixWebpackSourcePaths: true, 'report-config': { html: { outdir: 'html' } } }, coverageReporter: {type: 'lcovonly',dir: 'coverage/'}, nyanReporter: { renderOnRunCompleteOnly: process.env.CI } }) }上 Coveralls 网站开启需要测试的仓库,会得到一个 TOKEN。在 Travis 上同样开启这个仓库,在该项目设置中 Environment Variables 下添加环境变量 COVERALLS_REPO_TOKEN,值为上面的 Token。在 Travis CI 上启用浏览器Travis CI 是命令行环境,默认跑不了 GUI 程序,我们需要做些配置。新建 .travis.ymllanguage: node_js node_js: - 10.6.0 before_install: - "export DISPLAY=:99.0" - "sh -e /etc/init.d/xvfb start" addons: firefox: latest chrome: stable script: - yarn build - yarn test其中浏览器可以根据插件的文档选择不同版本。 Chrome: https://docs.travis-ci.com/user/chrome Firefox: https://docs.travis-ci.com/user/firefox 添加 Badges 到 README最后我们把结果放到 README 上以展示这个项目的可靠性。比如我的一个项目 get-selection-more [](https://www.npmjs.com/package/get-selection-more) [](https://travis-ci.org/crimx/get-selection-more) [](https://coveralls.io/github/crimx/get-selection-more?branch=master)将 crimx/get-selection-more 换成你的项目名称即可。最后这就是搭建 Karma 全家桶的基本流程,现在只需在 test 下新建 **/*.spec.ts 即可开始编写测试。由于每个工具各司其职,要更换或者添加新功能都非常容易。希望本文能帮助大家减少编写测试的困难,舒服地进行开发,产出更多可靠的项目。
2024年08月24日
3 阅读
0 评论
0 点赞
1
...
64
65
66
...
125