Git Product home page Git Product logo

blog's Introduction

  • 👋 Hi, I’m @chenwangji
  • 👀 I’m interested in web and native developing.
  • 📫 You can reach me at [email protected].

blog's People

Contributors

chenwangji avatar

Stargazers

 avatar  avatar

Watchers

 avatar  avatar

blog's Issues

友链

阁子

link: //www.himiku.com
cover: //i.loli.net/2018/12/15/5c14f2390c3bd.png
avatar: //i.loli.net/2018/12/15/5c14f1bb58a6d.jpg

关于

Origin

蝉時雨,源自日语 せみしぐれ。

夏日众蝉鸣叫此起彼伏好似落雨,蝉儿们似要将仅存的的生命燃烧奏出最后的音符,绚烂与壮美中氤氲着沉寂与无常,是日本夏天最具代表性的季节物语之一。

正如蝉儿一般,生命短暂即逝,却仍一无反顾奏出生命的最强音,而我的青春岁月又何尝不期望如此,在最美的年华绽放最璀璨的人间烟火。

蝉鸣如雨,花宵道中,一如少年。

利用 markdown 生成页面实践

为什么要用 markdown 来生成页面

对于展示型网站,例如官网这种场景,会有很多小的页面,运营会根据市场时刻有增删修改类似页面的需求,如果每次都响应运营的需求,不断地迭代增删页面,这个工作是很难终结的,运营不能及时看到页面,而开发会深陷在重复繁杂的工作中。

对于某些项目的详情页面,排版固定,可以提前制定样式,运营人员只需关注内容,而 markdown 的出现的目的就是让用户只关注内容而非排版。

市面上供用户使用,能生成 html 的编辑器多为富文本编辑器,富文本编辑器优点是样式可以自由定制,但在我们的官网场景中,样式的不可控反而成了其缺点,我们不可能让官网的展示凭用户喜好随意改动。

把页面的控制权交给运营,解放其他人员,是这个方案最大的出发点。

实现思路

运营人员后台通过 markdown 编辑器输出内容,通过预览或者保存后,在前台编译成 html,而 html 相关样式则是提前定制好的。

技术选型和实现流程

markdown 只是一种语法规则,可以按照一定标记编译成 html。
对于开发人员来说这最熟悉不过了,很多文档都是基于 markdown 直接生成的。

用户的输入需要在编辑器上展开,我们可以通过 code-mirror 来自己手撸一个,但是那样会耗费不少精力,投入产出比不高,而 markdown 编辑器的实现市面上有很多,经与产品确认,我们选择了一个比较小而精美的库 promarkdown

用户根据 md 语法规则在编辑器上输入内容后,点击预览/发布,会将整个表单内容以 json 的形式保存到后台,然后 h5 通过获取信息展示效果。

h5 拿到 json 数据后,利用 marked.js 会将 md 解析成 html, 再通过 html-to-react 转成 react node。

至于为什么要通过要转成 react node, 而不是直接用 HTML, 主要是因为有些 markdown 标签是需要解析成 react 组件的,并不全是 html。

而渲染这些 html 的 css 则是预先定好的

article {
  article {
    border-top: 1px solid transparent;

    section {
      // margin-bottom: 60px;

      // &:first-child {
      //   margin-top: 60px;
      // }

      &:last-child {
        margin-bottom: 60px;
      }

      & > *:not(img):not(.videoPlayer, h1) {
        margin: 0 20px;
      }

      & > *:not(:last-child, h1) {
        margin-bottom: 10px !important;
      }

      h1 {
        font-size: 28px;
        font-weight: 700;
        color: #333333;
        line-height: 40px;
        margin-top: 60px;
      }

      p {
        font-size: 16px;
        color: #333333;
        line-height: 28px;
      }

      img {
        display: block;
        margin: 0 auto;
        width: 100%;
      }

      .video-js {
        padding-top: 56.53vw !important;
      }

      em {
        font-style:italic;
      }

      table {
        font-size: 16px;
        color: #333;
        line-height: 28px;

        tr {
          height: 28px;

          td {
            vertical-align: top;
          }

          td:first-child {
            margin-right: 1em;
            padding-right: 1em;
            white-space: nowrap;
            font-weight: 500;

            &::after {
              content: ':';
            }
          }
        }
      }

      ol, ul {
        font-size: 16px;
        color: #333333;
        line-height: 28px;
      }

      ol {
        counter-reset: item 0;

        li {
          counter-increment: item 1;

          &::before {
            content: counter(item) '. ';
          }
        }
      }

      ul {
        li {
          &::before {
            content: '';
            font-weight: bold;
          }
        }
      }
    }
  }
}

这样就能得到我们想要得到的效果。

admin 端:

editor

展示端:
preview

流程如下:
process

遇到的问题及解决方法

如何扩展 markdown 不支持的标签,如视频

markdown 语法编译为 html 只支持部分标签,而视频并不支持,而且如果我们需要使用我们自己封装的组件,就需要做一些处理,比如我们需要在 markdown 中插入视频,就是在原有的 image 的解析方式来完成的

![video|封面图链接](视频链接)

这个规则默认会解析成 img 标签,但是我们可以这样自定义渲染方式:

import React from 'react'
import marked from 'marked'
import HtmlToReact from 'html-to-react'
import { VideoPlayer } from '@/components'

const imageParse = (href, title, text) => {
    if (text.indexOf('video') > -1) {
        const poster = text.substr(6)
        return `<%video poster='${poster}' src='${href}' />`
    }
    return `</p><img src="${href}" alt=""/><p>`
}


function formatVideo(source) {
    if (!source.match(/poster=/)) return htmlToReactParser.parse(source)
    else {
        const poster = source.match(/poster='(.*)' src=.*'/)[1]
        const src = source.match(/poster='.*' src='(.*)'/)[1]
        return <VideoPlayer poster={poster} src={src} key={src} />
    }
}

renderer.image = imageParse

export default (content) => {
    // ....
    try {
        const outputs = html.split(/<%video(.*)\/>/)
        formatted = outputs.map(o => formatVideo(o))
    } catch (error) {
        console.log(error)
    }
    // 返回解析内容
    return { formattedNode: formatted, toc }
}

如何生成目录树

拦截 marked 对标题的解析,并且将标题保存到一个数组里,即可实现目录树,并且实现锚点跳转。

// 生成目录树
const toc = defaultToc
const headingParse = (text, level, raw) => {
    const anchor = raw.toLowerCase().replace(/[^\w]+/g, '-')
    if (level >= 2) return `<h${level} id="${anchor}">${text}</h${level}>\n`
    toc.push({
        level,
        text,
    })
    return `<h${level} id="${text}">${text}</h${level}>\n`
}

如何让编辑器更好用

  • 接入阿里云 oss 上传,无需到阿里云管理端上传文件再获取链接
  • 实现预览功能

预览和发布的区别是,两个操作对应前端的两个不同路由,而预览的路由是只暴露给管理员,发布则是暴露给所有人,这样就实现了预览功能并且在前端共用了一套样式代码和 md 解析逻辑。

限制及相关取舍

  • 页面仅限于无特殊交互的纯展示页面,类似于 html 加上定制的 css,而无法加入 js 的控制
  • 如果要扩展 markdown 以外的标签,需要使用一些 hack 的方式去实现,比较麻烦

实现 Input 去除首尾空格

做项目遇到一个需求,要求所有的 input 框提交前自动去除首尾空格,全部都是空格就全部去除。

最简单的办法是在提交前对数据作处理,但是这样会带来很多的重复性工作,所以需要把这层逻辑抽取出来。

Vue 的做法

在之前用 Vue 开发的时候,官方提供了一个很方便方法,使用的 trim 装饰符:

<input v-model.trim="bindValue" />

React 上使用

然而目前用的是 React 开发,而 React 并没有原生提供类似功能,使用的 Antd 也没有提供,需要我们自己封装。

一般组件

如果都是 Input 组件,我们直接封装一层,加上去除首尾空格的逻辑即可。

去除的最佳时机应该是输入框失去光标的时候,所以应该监听 blur 事件。

具体代码如下:

// TrimInput Component

import React from 'react';
import { Input } from 'antd';

export default class TrimInput extends React.Component {
  handleBlur = (e) => {
    // 去除头尾空格
    e.target.value = e.target.value.trim();
    const { onChange } = this.props;
    // 主动调 onChange,将数据同步给 Form
    onChange(e);
  }

  render () {
    return (<Input onBlur={this.handleBlur} {...this.props} />)
  }
}

高阶组件

有时候并不一定都是 Input,也有可能是 Textarea, 这就要求我们把代码写得灵活一点,我们可以采用 React 特有的高阶组件扩展我们的逻辑。

具体代码:

// withTrim

import React, { Component } from 'react';

const withTrim = (WrappedComponent) =>
  class WrapperComponent extends Component {
    // 去除头尾空格
    handleBlur = (e) => {
      e.target.value = e.target.value.trim();
      const { onChange } = this.props;
      onChange(e);
    }

    render() {
      return <WrappedComponent onBlur={this.handleBlur} {...this.props} />;
    }
}

export default withTrim;

使用:

const TextArea = withTrim(AntdInput.TextArea);

循环处理异步操作

function delay(time) {
    return new Promise(resolve => setTimeout(resolve, time))
}

async function delayedLog(item) {
    await delay(1000)
    console.log('item')
}

串行

可以保证异步逻辑的顺序

async processsArray(array) {
    for (const item of array) {
        await delayedLog(item)
    }
    console.log('Done !')
}

processArray([1, 2, 3])
// 1
// 2
// 3
// Done !

并行

async function processArray(array) {
    const promises = array.map(delayedLog)
    await Promise.all(promises)
    console.log('Done !')
} 

注意:Array.prototype.forEach() 并不能实现。

vue cli 3 填坑记录

vue cli3 正式发布已经过去很久,甚至 vue-cli@4 也已经可以在官方仓库看到。

版本3 配置方式和之前版本差别巨大,需要踩过很多坑才能熟悉它的配置套路。

以下是一些踩坑的记录。

问题一:vue + ts 模版不会将 .js 通过 babel 转译

问题发现

项目一开始是通过命令行选择 vue + ts 生成。但是由于一些原因,我们部分文件还是沿用 js 来写。

当跑在比较旧的手机上时,检测到有报错,分析报错信息是因为 es6 扩展运算符导致的。让人比较诧异的是,这个配置下,vue cli 并没有默认去编译 js 代码。

这种情况下,检查 vue-cli 的默认配置就显得很有必要了。

我们可以通过执行在 package.json 的 script 上增加一个命令:

"scripts": {
    "inspect": "vue-cli-service inspect > output.js"
  },

这样,vue-cli3 的默认配置就可以导出到 output.js 中:

/* 省略其他 */

  /* config.module.rule('ts') */
  {
    test: /\.ts$/,
    use: [
      {
        loader: 'cache-loader',
        options: {
          cacheDirectory: '/Users/chenwangji/Desktop/work/project_work/ScratchBlockly/node_modules/.cache/ts-loader',
          cacheIdentifier: '48924560'
        }
      },
      {
        loader: 'ts-loader',
        options: {
          transpileOnly: true,
          appendTsSuffixTo: [
            '\\.vue$'
          ],
          happyPackMode: false
        }
      }
    ]
  },
  /* config.module.rule('tsx') */
  {
    test: /\.tsx$/,
    use: [
      {
        loader: 'cache-loader',
        options: {
          cacheDirectory: '/Users/chenwangji/Desktop/work/project_work/ScratchBlockly/node_modules/.cache/ts-loader',
          cacheIdentifier: '48924560'
        }
      },
      {
        loader: 'ts-loader',
        options: {
          transpileOnly: true,
          happyPackMode: false,
          appendTsxSuffixTo: [
            '\\.vue$'
          ]
        }
      }
    ]
  }

果然,这个配置下只会对 .ts, .tsx, .vue 文件执行 ts-loader 的编译(ts-loader 会内部会经过 babel 编译)。

所以我们需要手动增加对 .js 的编译支持。

解决方法

我们可以通过配置 babel-loader 来编译 js 文件。查看 webpackbabel-loader 的配置,我们需要完成以下配置:

module: {
  rules: [
    {
      test: /\.m?js$/,
      exclude: /(node_modules|bower_components)/,
      use: {
        loader: 'babel-loader',
        options: {
          presets: ['@babel/preset-env']
        }
      }
    }
  ]
}

所以我们需要运行以下命令安装依赖:

yarn add -D babel-loader @babel/core @babel/preset-env

我们知道可以通过 vue.config.js 去修改 webpack 的配置,不过 vue-cli 3 配置方式改为了通过 webpack-chain,配置起来既麻烦又很不清晰(难道是为了不让别人轻易去修改默认配置吗?),不过大体可以通过 vue-cil 官网去了解配置。

所以以上配置配置在 vue.config.js 中的配置如下:

chainWebpack: config => {
    config.module
        .rule("js")
        .test(/\.m?jsx?$/)
        .exclude
        .add(filepath => {
            return /node_modules/.test(filepath)
        })
        .end()
        .use('babel-loader')
        .loader(require.resolve('babel-loader'))
        .tap(() => Object.assign({}, { presets: [
            '@babel/preset-env',
        ] }))
}

配置之后再执行构建,项目中的 .js 代码成功编译,老旧手机浏览器报错问题解决。

配置 webpack 生成可调试的代码

问题发现

开发模式下 .vue 文件编译后是这样的:

/* hot reload */
if (module.hot) {
  var api = require("/Users/chenwangji/Desktop/work/project_work/ScratchBlockly/node_modules/vue-hot-reload-api/dist/index.js")
  api.install(require('vue'))
  if (api.compatible) {
    module.hot.accept()
    if (!module.hot.data) {
      api.createRecord('656621cd', component.options)
    } else {
      api.reload('656621cd', component.options)
    }
    module.hot.accept("./Index.vue?vue&type=template&id=656621cd&", function () {
      api.rerender('656621cd', {
        render: render,
        staticRenderFns: staticRenderFns
      })
    })
  }
}
component.options.__file = "src/coding/views/editor/Index.vue"
export default component.exports

文件目录是这样的:

文件目录

这其实也不是问题,应该是 feature, 但是这个 feature 让我们开发调试极其困难,所以我们需要让开发环境文件按照原来的目录去生成。

解决方法

通过查阅文档,我们可以通过以下方式配置:

configureWebpack: config => {
    // 根据环境变量修改打包文件名
    const production = process.env.NODE_ENV === 'production'
    if (!production) {
        config.output.devtoolModuleFilenameTemplate = info => {
            const resPath = info.resourcePath
            if ((/\.vue$/.test(resPath) && !/type=script/.test(info.identifier)) || /node_modules/.test(resPath)) 
            {
                return `webpack:///${resPath}?${info.hash}`
            }
            return `webpack:///${resPath.replace('./src', 'uncompiled-code/src')}`
        }
    }
}

这样就可以达到我们想要的效果,调试起来就会方便很多。

禁止图片转为 base64

问题发现

由于某个库不支持 base64 的图片,所以需要配置让图片全部不转 base64。

解决方法

需要更改 url-loader 配置。

// 图片不转 base64
chainWebpack: config => {
    config.module
        .rule('images')
        .use('url-loader')
        .loader('url-loader')
        .tap(options => Object.assign(options, {limit: 1})) // 这里 limit 如果配置为 0 反而是会把所谓图片转为 base64,所以配置为 1
},

To be continued...

手动实现(一):new 和 instanceof

新的一年,新的开始。最近的开发过程越来越感觉基础不够扎实,很多知识的理解都很片面,所以打算以这一个系列开始自己的基础夯实之路,希望自己能坚持下去。

手动实现 new

new 的原理

new 的作用是实例化一个类,所以紧接的必须是一个构造函数(或者 class)。

具体执行过程:

  1. 创建一个空对象
  2. 将该对象的 __proto__ 属性指向构造函数的 prototype
  3. 构造函数的执行上下文指向该对象,然后执行构造函数
  4. 如果构造函数的返回结果是对象,则直接返回这个对象;否则隐式返回最开始创建的那个对象。

具体代码实现如下:

/**
 * 手动实现 new 操作符
 * @param {*} fn 构造函数,第二个及后面的参数传递给构造函数
 */
function New(fn) {
    const instance = {}
    instance.__proto__ = fn.prototype

    const args = Array.prototype.slice.call(arguments, 1)
    const res = fn.apply(instance, args)

    // 如果构造函数有返回值并且为对象时,直接用该返回值,否则返回前面生成的实例
    if (res !== null && (typeof res === 'object' || typeof res === 'function')) {
        return res
    }
    return instance
}

// test
function Dog(name) {
    this.name = name
}

var dog = New(Dog, 'Tony')
console.log(dog.name) // 'Tony'
console.log(dog instanceof Dog) // true

手动实现 instanceof

instanceof 原理

instanceof 用于判断左侧值是否是右侧值的实例,所以左侧必须是一个对象,而右侧是一个类。

具体原理是 instanceof 会查找原型链,知道 null 之前如果还不是这个对象的实例则会返回 false,否则返回 true

具体实现:

/**
 * 
 * @param {*} instance 实例
 * @param {*} clazz 类
 */
 function instanceOf(instance, clazz) {
    if ( typeof clazz !== 'function') throw new Error('Right-hand error')
    if (instance === null || (typeof instance !== 'object' && typeof instance !== 'function')) return false

    const proto = clazz.prototype
    while (instance.__proto__) {
        if (instance.__proto__ === proto) return true
        instance = instance.__proto__
    }
    return false
 }

 // test
 var one = new Number(1)
 console.log(
    instanceOf(one, Number), // true
    instanceOf(one, Object) // true
 )

React 系统中,在离开编辑页面前做提示

在做有编辑器相关需求的时候,遇到一个之前没遇到过的需求:用户离开当前页面前,需要提示用户保存。

由于是用 react 开发的单页应用,涉及到离开当前页面的场景会有:

  1. 用户系统内跳转到其他路由
  2. 用户点击浏览器上的刷新、关闭浏览器标签页等操作

下面分开解决以上问题。

路由跳转

路由跳转会导致组件卸载,我们自然而然会想到在 react 组件的 componentWillUnmount 生命周期中做一些提示,但是使用这个方法会有个问题:这里并没有办法阻止路由的跳转,这个过程是不可终止的,所以并不能满足需求。

我最开始的想法是在 react 生态中寻找类似于 Vue Router 的 导航守卫 来实现对路由跳转的拦截

beforeRouteLeave (to, from , next) {
  const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
  if (answer) {
    next()
  } else {
    next(false)
  }
}

这个离开守卫通常用来禁止用户在还未保存修改前突然离开。该导航可以通过 next(false) 来取消。

而在 react-router 之前的版本也是存在路由守卫的,但是很遗憾,react-router-dom v4 去除了相关 api,但是我们可以通过其提供的 <Prompt/> 组件来实现。

import {Prompt} from 'react-router-dom';

// 只需把这个组件放在页面中,即可实现路由跳转前的拦截提示

<div>
    <Prompt
        when={true}
        message={() => '离开当前页后,所编辑的数据将不可恢复'}
    />
</div>
  • when 可以动态改变是否需要提示
  • message 为提示消息

这样,只要是跳转到其他路由都会触发这个提示。

效果如图:

Prompt

浏览器级刷新或者关闭页面

当浏览器窗口关闭或者刷新时,会触发 beforeunload 事件。当前页面不会直接关闭,可以点击确定按钮关闭或刷新,也可以取消关闭或刷新。

如果处理函数为 Event 对象的 returnValue 属性赋值非空字符串,浏览器会弹出一个对话框,来询问用户是否确定要离开当前页面。有些浏览器会将返回的字符串展示在弹框里,但有些其他浏览器只展示它们自定义的信息。没有赋值时,该事件不做任何响应。

我们只需在页面组件上绑定该事件,即可完成该功能。

const listener = e => {
    e.preventDefault();
    e.returnValue = '离开当前页后,所编辑的数据将不可恢复' // 浏览器有可能不会提示这个信息,会按照固定信息提示
}

// ...

componentDidMount() {
    window.addEventListener('beforeunload', listener)   
},

componentWillUnmount() {
    window.removeEventListener('beforeunload', listener)   
}
// ....

效果如图:

beforeunload

最后,我们可以把这两部分逻辑抽象起来,形成一个需要具有该特定功能的组件,不过这部分就不贴出来了,与主题相关度不足。

[翻译] 我在阅读 MDN 的时候发现的三个 input 的属性

最近我在浏览 T�witter 的过程中偶然看到了 Dan Abranmov 的一条推特。他分享的一个代码片段引起了我的注意。代码里用 JavaScript 获取了 DOM 的 input 元素,并读取或改变它的一些属性,其中最让我激动和好奇的是 defaultValue 属性。

我立即打开 MDN 去阅读更多关于 HTMLInputElements 的这个属性然后又偶然发现更多我之前没有注意到的属性,从而促使我写下了这篇小文章。

那就让我们一起去看看吧!

作者:Stefan Judis

原文链接:https://dev.to/stefanjudis/three-input-element-properties-that-i-discovered-while-reading-mdn-30fg

defaultValue

这是 Dan 的推特的例子 -- 我们来快速了解一下。假定你已经有一些 HTML,然后我们获取一个 input 元素,这个元素有一个 value 属性(特性(attributes)是指定义在 HTML 中然而它的属性(properties)属于 JavaScript 对象)。

<input type="text" value="Hello world">

你可以获取这个元素然后开始对其做些操作。

const input = document.querySelector('input');

console.log(input.value);        // 'Hello world'

input.value = 'New value';

console.log(input.value);        // 'New value'
console.log(input.defaultValue); // 'Hello world'

你可以看到 HTML 中 value 属性已经初始化并且映射到元素的 value 属性。这个我完全理解。现在当你改变 value, 你仍然能通过 defautlValue 获取 “初始值”(checkbox 可以使用 defaultChecked)。这相当酷!

以下是 MDN 对 defaultValue定义

在 HTML 创建对象的时候返回/设置默认值作为初始化声明。

如果喜欢你可以在 CodePen 上试试。

indeterminate

indeterminate 也是一个很棒的属性(property)。你知道 checkbox 除了被选中和非被选中外还有另外的一个视觉上的状态吗?indeterminate 是一个属性(property)(没有与之对应的特性(attribute))。你可以在 checkbox 中显示一个你时常看到的小横杠。

const input = document.querySelector('input')
input.indeterminate = true

img

indeterminate �设为 true 并不会对 checkbox 的值产生什么影响,它的唯一合理的用处应该是当我们嵌套 checkbox 的时候,比如��像 Chris Coyier describes on CSSTricks

indeterminate 并不只是在 checkbox 上有用,它也可以用于 radio 按钮或者 progress 元素。我们来一组 radio 按钮,这些 radio 都没有被选中。当你没有预先选择单选框组的任一元素,没有元素被选中过,也没有被取消选中的 - 所以它们都是 indeterminate 状态。

比较酷的是,你还可以用 css 伪类选择器 :indeterminate 来选择元素,来方便地展示当 radio 没有被选中时的一些特别的 UI 组件。

img

.msg {
  display: none;
}

input:indeterminate ~ .msg {
  display: block;
}

关于 indeterminate 属性比较有意思的一点是,你将其�设为 truefalse ,这会影响到 checkbox 的伪类,但不会影响到 radio 的。处理 radio 的实际选中状态永远是对的。

顺便说下,已经处完成状态的 progress,并且没有定义 �value 特性(attribute) 元素也会匹配 :indeterminate 选择器。

以下是 MDN 对 indeterminate定义

[它]代表 checkbox 或者 radio 按钮没有值(value),处于 indetermiate 状态。checkbox 会改变其外观为一种��类似于第三种状态。�不会影响到元素的 value 或者 checked 属性。点击 checkbox 会设置其值为 false。

如果�喜欢,你可以在 CodePen 上操作尝试。

selectionStart, selectionEnd 和 selectionDirection

这三个属性可以找出用户的选中部分,我们可以直接使用它们。如果用户在 input 框中选中了一些文本,我们可以用这些属性分析被选中的东西。

img

const input = document.querySelector('input')

setInterval( _ => {
  console.log(
    input.selectionStart,
    input.selectionEnd,
    input.selectionDirection
  ) // e.g. 2, 5, "forward"
})

我做了一个测试,我定义了一个每秒循环打印选中值的定时器,selectionStartselectionEnd会返回我所选中的内容的描述位置的数值,但是当你用你的鼠标或者触控板(trackpad), selectionDirection 出人意料地返回了 none,但是当你用 SHIFT 和 箭头或者控制按键去选择文字,它就会返回 forwardbackward

如果�喜欢,你可以造 CodePen 上操作尝试。

然后就是这些啦。:)

快速(短)小结

MDN 是一个非常棒的资料来源。甚至在我使用了 input 元素八年后的现在依然有新的东西可以挖掘。这就是我热爱 web 开发的原因。对我个人来说,我经常随机阅读 MDN 上的文章(我有一个日常懒惰机器人(Slack-bot)来提醒我打开 bit.ly/randommdn),因为这上面一定会有东西可以发掘,并且,我高度推荐这个。

谢谢阅读!

译者注

attribute 和 property 的区别

这里文中 attribute 指的是 HTML 中元素的属性,中文译作特性。property 指对象的属性,中文译作属性。

常用 vscode 插件记录

css 类名自动补全:
HTML to CSS autocompletion
HTML to CSS autocompletion
IntelliSense for CSS class names in HTML
HTML CSS Support

书单

ES6 标准入门

author:阮一峰
published:2017-09-01
progress:正在阅读...
rating:5,
postTitle:
postLink:
cover://i.loli.net/2018/12/10/5c0dcb80d3615.jpg
link://www.duokan.com/book/169714
desc:柏林已经来了命令,阿尔萨斯和洛林的学校只许教 ES6 了...他转身朝着黑板,拿起一支粉笔,使出全身的力量,写了两个大字:“ES6 **!”(《最后一课》)。

使用 travis CI 和 gh-pages 快速部署页面

之前在使用 gh-pages 自动部署页面后,想着更进一步,希望能一步到位,直接 push 到远程仓库的同时,也直接把对应的站点部署了。其实这也是现在很多项目的做法。

而实现这一步的最简单的一个工具就是 travis CI

背景知识

具体关于 travis CI 的介绍,可以参考:

稍微了解这个工具之后,就可以动手实践。

实践

可以跟着这篇文章一点都不高大上,手把手教你使用Travis CI实现持续部署一步步走下去。

遇到的问题

跟着这篇文章走下去,在之前一般不会遇到坑。

但是我就遇到了。

下面是这个报错日志:

remote: Invalid username or password.
fatal: Authentication failed for 'https://github.com/chenwangji/recipe.git/'
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! [email protected] deploy: `gh-pages -d build`
npm ERR! Exit status 1
npm ERR! 
npm ERR! Failed at the [email protected] deploy script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

开始以为是因为没有指定 git 用户名和邮箱导致的,于是在 .travis.ymlscript 中加入命令:

- git config --global user.email "[email protected]"
- git config --global user.name "chenwangji"

重新构建依然报错。

后来网上看到这篇文章,里面有段话解释了这个问题:

刚开始Build过程很顺利,但在最后的deploy阶段遇到了问题。使用hexo-deployer-git部署到github时总是报以下错误:

remote: Invalid username or password.
fatal: Authentication failed for 'https://github.com/dennis-xlc/dennis-xlc.github.io.git/'
FATAL Something's wrong. Maybe you can find the solution here: http://hexo.io/docs/troubleshooting.html
Error: remote: Invalid username or password.
fatal: Authentication failed for 'https://github.com/dennis-xlc/dennis-xlc.github.io.git/'
    at ChildProcess.<anonymous> (/home/travis/build/dennis-xlc/dennis-xlc.github.io/node_modules/hexo-deployer-git/node_modules/hexo-util/lib/spawn.js:42:17)
    at ChildProcess.emit (events.js:110:17)
    at maybeClose (child_process.js:1015:16)
    at Process.ChildProcess._handle.onexit (child_process.js:1087:5)

一开始我一直以为是Deploy Key的问题,但重新添加Deploy Key和加密Private Key多次,这个问题还是没有解决。后来检查hexo-deployer-git的配置发现repo项写的是https协议的地址:

deploy:
  type: git
  repo: https://github.com/dennis-xlc/dennis-xlc.github.io.git

查看了github的官网后才知道使用https协议的地址,在git push时需要提供Github的用户名和密码。

When you git fetch, git pull, or git push to the remote repository using HTTPS, you’ll be asked for your GitHub username and password.

From Githubhelp.github.com/articles/which-remote-url-should-i-use/#cloning-with-https-recommended

解决问题

解决办法是将 github 地址从 https 改为 ssh

但是 gh-pages 如果直接使用命令行方式

    "predeploy": "npm run build",
    "deploy": "gh-pages -d build"

其默认仓库地址是当前仓库的地址。

所以一个方法是用 git 更改远程地址,另一个方式是重写一个 gh-pages 的执行脚本。

这里我选择第二种。

所以这里参照 gh-pages 写了以下脚本:

/**
 * 因为 gh-pages 默认仓库地址是 https, 但是 travis CI 里,用 https 会报 `remote: Invalid username or password.`
 * 参考 https://www.hidennis.tech/2015/07/07/deploy-blog-using-travis/
 * 将 github 地址改为 '[email protected]:chenwangji/recipe.git'
 * 最终能解决问题。
 */

var ghpages = require('gh-pages')
var REPO = '[email protected]:chenwangji/recipe.git'

function callback(e) {
  if (e) {
    console.err(e)
    return
  }
  console.log('deploy succeed !')
}

ghpages.publish('build', {
  repo: REPO,
}, callback)

再去修改 package.json 中的 scripts 字段:

  "scripts": {
    "predeploy": "npm run build",
-   "deploy": "gh-pages -d build"
+   "deploy": "node gh-pages.js"
  },

问题即解决。

webpack 配置优化

提高打包速度

  • 针对耗时 loader,如 vue-loader, babel-loader 配置 cache-loader, thread-loader
  • 合理使用 mode 参数和 source-map 参数
  • 缩小文件的搜索范围(配置 include exclude alias noParse extensions)
  • 使用 happy-pack 开启多进程 loader 转换
  • 使用 webpack-parallel-uglify-plugin 增强代码压缩
  • 使用 DllPlugin 和 DllReferencePlugin 抽离第三方模块

优化打包文件体积

  • 使用 webpack-bundle-analyzer 分析打包后的文件
  • 对通过 script 外部引入(如 CDN)的库,我们在使用时依然通过 import 方式引入,但是不希望 webpack 将其打包进来,我们就可以通过配置 externals 的方式解决这个问题
  • tree shaking

ES6 新增基本数据类型--Symbol

Symbol 的作用

对象属性私有化

如果没有 Symbol,可以这样实现,

利用闭包:

var Person = (function () {
    var _gender = '';
    var P = function (name, gender) {
        this.name = name;
        _gender = gender;
    }
    P.prototype.getGender = function () {
        return _gender;
    }
    return P
})()

var p1 = new Person('小明', '男');
p1.gender // undefined
p1.getGender() // '男'

使用 Symbol, 具体代码:

var Person = (function () {
    var s = Symbol('gender');
    var P = function(name, gender) {
        this.name = name;
        this[s] = gender;
    }
    return P
})()
var p2 = new Person('小明', '男');
p2.gender // undefined
p2[Symbol('gender')] // undefined

iPhone 刘海屏手机适配

安全区域

安全区域指的是一个可视窗口范围,处于安全区域的内容不受圆角(corners)、齐刘海(sensor housing)、小黑条(Home Indicator)影响

由于刘海屏和底部 indicator 的存在,我们的兼容工作就是需要让可操作区域处于安全区域内。

viewport-fit

通过对meta标签viewport的扩展,可以调整页面的展现区域。viewport-fit有三个可选值:

  • contain:使页面展示在安全区域内
  • cover: 使页面占满屏幕
  • auto: 和 contain 选项表现一样

使用方式:

<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"/>

检测是否是 iphone x series

iphone x 系列手机的特点是特别长,所以可以通过这个特点去检测是否是 iphone x series

/**
 * 是否是 iphone x 系列手机
 */
export function isIphonexSeries() {
    if (typeof window !== 'undefined' && window) {
        // 因为浏览器模拟器和真机对横屏下 window.screen.width 表现不一,需兼容
        const screenWidth = Math.max(window.screen.width, window.screen.height)
        return /iphone/gi.test(window.navigator.userAgent) && screenWidth >= 812;
    }
    return false;
}

然后在 body 上增加 iphonx class

import { isIphonexSeries } from '@/utils/common'

export const addIphonexClass = () => {
    const root = document.querySelector('body') as Element
    if (isIphonexSeries()) {
        console.log(root)
        root.classList.add('iphonex')
    }
}

注入

window.onload = () => {
   // 增加 iphonex 标识 class
    addIphonexClass()
}

样式兼容

// iphonex.less

.iphonex {
    // styles
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.