被遗忘的 Loader

源码

专门介绍 Webpack 那些冷门的 Loader

Webpack 的官方文档上有一个 list-of-loaders
之是其中的大部分被使用到的机会非常少, 好心疼这些缺 :heart: 的模块, 所以有这个 repo 专门介绍这些冷门 Loader .

demo

raw-loader

作用:以 utf-8 编码加载文件的内容

json-loader

作用:加载一个JSON文件的内容并返回对象.

在 Node.JS 环境下可以 require 一个 .json 格式的文件:

1
require('./config.json')

但是在 wepack 打包的时候不可以直接 require 一个 .json 的文件. 这是因为 webpack 会把 .json 文件当成是 .js 文件来解析导致报错.
因此正确的做法是使用 json-loader

1
require('json!./config.json')

hson-loader

作用:加载增强版的JSON (HSON) 并返回对象.

HSON 对 JSON 的增强体现在:

  • 所有的 JSON 字符串都是有效的 HSON 字符串
  • HSON 里可以写注释
  • HSON 里属性名不一定要有引号

script-loader

有的时候不希望 webpack 在打包某些文件的时候进行解析, 而是希望在浏览器中直接运行.
一个不常见的例子是某段代码重新定义了 require , 如下:

browser_require.js

1
2
3
4
window.require=function(){
console.log(arguments);
}
require(2);

引入该文件的正确方法是:

1
require('script!./browser_require.js')

这样该文件内容里的 require 就不会被 webpack 解析了, 使用 script-loader 整个文件会被转成字符串打到包里,最终在浏览器里通过 eval 来执行.

使用 Fetch API 实现前后端共用同一个 SDK

源码

架构设计实践: 使用 Fetch API 实现前后端共用同一个 SDK.

做过一段时间的 Web 开发以后,就会有这样的感觉,前后端分离几乎和吃饭睡觉一样平常. 前端专注浏览器的一切,
通过 API 和后端通信,而后端工程师专注于系统的执行效率,可扩展性等问题.

很多优秀的网站特别是有一部分面向开发者的网站都会提供 API 的 SDK.

  • 封装底层发起请求的方法
  • 异步请求成功和出错的处理
  1. 封装

浏览器端发送 AJAX 请求底层一般采用 XMLHttpRequest 来实现, 实际工作中常使用具体的封装良好的类库,
例如 jQuery, superagent 等. 而后端 Node.js 经常用的是基于 Stream 的解决方案, 由于 API 是很常见的
工作,所以已经封装到了 Node.JS 的核心模块 http 里, 此外比较常用的第三方的类库有 request.

如果前后端公用一个 SDK, 那么必然要在上层引入一定的设计模式,来封装底层具体实现:

1
2
3
4
5
if(isBrowser){
return facade(new XMLHttpRequest(...))
}else{
return facade(http.request(...))
}

在浏览器端发起 AJAX 请求现在有了一套新的API, 那就是 Fetch. 与基于 XMLHttpRequest 的方法相比,它有几个显著地优势.

  • 更安全

虽然 Fetch 目前只在 Chrome 等比较新的浏览器上可以使用. 不过别担心, 已经有好几个垫片可以解决兼容性的问题.

  1. 异步

不管是在客户端还是在服务器端,对请求的处理都是异步的,这是由 JavaScript 本身的特性决定的.在很长的一段时间里
回调函数一直都是处理异步问题首选的方案,但是在经过了这么多年的折磨之后,人们终于想到了一个非常优秀的替代方案 - Promise.

1
2
3
4
5
6
7
8
var api=require('my-awesome-api');
api.repos('github')
.then(res=>{
// 成功
})
.catch(err=>{
// 失败
})

如果在客户端和在服务器端能够采用一致的方案,这无疑会大大提高 JavaScript 代码的质量和开发效率.

公用 SDK 有很多好处:

  • 浏览器端专注于路由,页面的设计,把请求的封装处理交给 SDK
  • 后端的开发者可以使用 SDK

所以是时候尝试和采用 Fetch API 了.

关于 Fetch API 简单的介绍

http://github.github.io/fetch/

实现该模式的 SDK

https://github.com/ringcentral/ringcentral-js

关键模块

https://github.com/matthew-andrews/isomorphic-fetch

如何使用 ES6 编写一个 React 模块, 并且编译后发布到 NPM.

源码

目录

前言

前端开发果真是发展迅猛,刚享受到由模块化,组件化和单元测试带来的种种好处,又得迅速拥抱 Grunt, Gulp, Browserify, Webpack 这类自动化工具的变革。
除了工具和生态圈,JavaScript 本身也在飞速发展着。ES2015(ES6) ,ES2016(ES7) … 照这样的节奏,几乎是一年一个标准。标准多了,为解决兼容性的问题,
竟也派生出了 源代码编译 的概念。前端开发者通过语法糖、转化器、Polyfill 等,可以享受到标准乃至尚未定稿草案里的规范的便利,大幅提升开发效率。

如果你在使用 React, 那么肯定已经撸了好多自己的组件, 并尝试着共享出来。在 OneAPM 前端开发过程中, 我们也曾遇到了一些组件共享的问题:

例如:

  • 是通过 git 直接发布还是通过 NPM 发布 ?
  • 发布的是 ES5 的代码还是 ES6 的代码 ?
  • 如何解决 Babel5 和 Babel6 的冲突 ?

这篇文章会通过编写一个叫做 MyComponet 的组件来演示发布一个 React 组件需要注意的地方, 并不涉及单元测试和代码规范等。
至于这个模块本身,它的功能特别简单, 就是显示模块自身的的属性。

源代码

我们来编写 MyComponent 组件,放到项目的 src 目录下。

src/MyComponent.jsx

1
2
3
4
5
6
7
8
9
10
import React from 'react';

const MyComponent = props=> {
return <div>
props:
<pre>{JSON.stringify(props, null, 2)}</pre>
</div>
}

export default MyComponent;

关于各种文件放在哪里, 这里是我推荐的一些约定:

  • src 下用于存放源代码
  • lib 是编译后的代码,这个目录只读
  • 所有包含 ES6 语法的文件名统一后缀为 .es6
  • 所有包含 JSX 语法的文件后统一缀名为 .jsx

假设源代码里还有另外两个文件 foo.es6bar.js,简化起见都丢到 src 的根目录下。

编译

Babel

为了把 ES6 代码编译成 ES5,需要安装 Babel,这个工具可以说野心极大,一次编译可以让 JavaScript 运行在所有地方。(听起来是不是有点 Java 的作风)

目前最常用的是 Babel5 版本,但是 Babel6 版本的设计更为精巧,已经非常推荐更新。也正是由于 Babel 有两个版本,所以开发过程中很有可能遇到这样的情况,
模块 A 的开发依赖于 Babel5 版本,而模块 B 依赖于 Babel6 版本。

解决这个问题最好的做法就是把 A 和 B 拆开,独立开发和发布。并且在发布到 NPM 的时候发布是的编译后的,也就是 ES5 版本的代码。

所以如果你的机器上的 babel 是全局安装的,是时候卸载它了,因为它的版本不是 5 就是 6 ,会导致一些不可预见的问题。

npm uninstall babel-cli --global

正确的安装方式是把 babel-cli 作为 development 依赖

npm install babel-cli --save-dev

使用的时候并不是直接调用全局的 babel 而是调用依赖里的 babel 可执行文件

./node_modules/.bin/babel

如果按照前文的约定来组织代码,src 目录结构看起来是这样的

1
2
3
4
src
├── bar.js
├── foo.es6
└── MyComponent.jsx

模块所有的代码都在一个目录下,这样编译过程就简单多了,两条命令就可以完成

1
2
./node_modules/.bin/rimraf lib
./node_modules/.bin/babel src --copy-files --source-maps --extensions .es6,.es,.jsx --out-dir lib

输出目录的结构

1
2
3
4
5
6
lib
├── bar.js
├── foo.js
├── foo.js.map
├── MyComponent.js
└── MyComponent.js.map

命令详解

具体解释一下各个命令的作用:

第一条命令 ./node_modules/.bin/rimraf lib

作用 编译前清空之前的 lib 目录,这是一个好习惯,可以杜绝对 lib 下的文件的任何手动更改。

第二条命令

./node_modules/.bin/babel src --out-dir lib --source-maps --extensions .es6,.es,.jsx --copy-files

作用 遍历 src 目录下的文件,如果后缀名是 .es/.es6/.jsx 中的一种,就编译成 ES5,否则就直接拷贝到输出目录 lib 下

参数详解:

--out-dir lib 指定输出目录为 lib

--extensions .es6,.es,.jsx 指定需要编译的文件类型

--copy-files 对于不需要编译的文件直接拷贝

--source-maps 生成 souce-map 文件

.babelrc 文件

编译过程中还隐含了一个步骤就是加载 .babelrc 文件里的配置,该文件内容如下

1
2
3
4
5
6
7
{
"presets": [
"es2015",
"stage-0",
"react"
]
}

这是因为 Babel6 采用了插件化的设计,做到了灵活配置:如果要转换 JSX 语法文件,就加上 React 的 preset,同时项目依赖里要添加
babel-preset-react

1
npm install babel-preset-react --save-dev

样例代码

开发和调试 React 模块目前最好用的打包工具还是 Webpack,在项目跟目录下,新建一个 example 目录:

example/index.html

1
2
3
4
5
6
7
8
9
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<title>Example</title>
</head>
<body></body>
<script src="bundle.js"></script>
</html>

example/src/index.jsx

1
2
3
4
5
6
7
import React from 'react';
import MyComponent,{foo,bar} from '../../';
import {render} from 'react-dom';

var element = document.createElement("div");
document.body.appendChild(element);
render(<MyComponent name="myComponent"/>, element);

webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var path = require('path');

module.exports = {
entry: path.join(__dirname, 'example', 'src', 'index.jsx'),
output: {
filename: 'bundle.js'
},
module: {
loaders: [{
test: /\.jsx$/,
loader: 'babel',
include: [
path.join(__dirname, 'example')
]
}]
},
devServer: {
contentBase: path.join(__dirname, 'example')
}
}

运行样例代码

1
./node_modules/.bin/webpack-dev-server

发布

发布前,还有一件事就是为你的模块添加一个入口文件 index.js,全部指向编译后的代码

1
2
3
4
module.exports = require('./lib/MyComponent');
exports.default = require('./lib/MyComponent');
exports.bar = require('./lib/bar');
exports.foo = require('./lib/foo');

接下来就可以发布到 NPM 了。

1
npm publish

使用

别的开发者如果想使用你新发布的模块的时候可以直接通过 NPM 安装

1
npm install react-component-example --save-dev

然后在父级项目的代码里导入模块

1
import MyComponent,{foo,bat} from 'react-component-example'

此时导入的直接是 ES5 代码,跳过了组件的编译过程从而避免了出现组件 Babel 版本和父级项目 Babel 版本不一致的问题,并且速度更快,是不是很棒!

使用源码

假设你的模块包含很多组件,开发者可能只想用其中的一个或某几个,这时可以这样导入:

1
import MyComponent from 'react-component-example/src/MyComponent.jsx'

这种情况下,导入的是 ES6 代码,并且会被加入父级项目的编译过程。此外,父级项目在编译这个文件的时候会读取组件的 .babelrc 配置文件。

样式

推荐每个模块在发布的时候把样式文件 style.css 放在根目录下,如果使用的是预处理工具 LESS 或者 SASS, 同样把处理前后的样式文件一起发布。

关于

本文使用的 babel 版本

./node_modules/.bin/babel --version 6.4.5 (babel-core 6.4.5)

LICENSE

MIT

React 中集成 Datatables

源码

react-datables-example

Example for Datatables usage with React and Webpack

This example will mainly focus on how to use Datatables and its extensions in React project instead of diving into the fabulous API.

How to run this example

1
npm install && npm start

Open your browser and navigate to http://localhost:8080/static/entry

How to import Datatables ?

1
2
import $ from 'jquery';
import 'datatables.net';

After being imported, you can initialize a table DOM element normally

$(elem).dataTable(options) or $(elem).DataTable(options)

Is it possible to use DataTable object directly ?

No, because it is deeply coupled with jQuery, it requires jQuery context to intialize the table.

In fact, the DataTable object is imported by default.

1
2
3
import $ from 'jquery';
import DataTable from 'datatables.net';
console.log(DataTable === $.fn.dataTable); // true

How to import Bootstrap styling ?

1
2
3
import 'bootstrap/dist/css/bootstrap.css';
import 'datatables.net-bs/js/dataTables.bootstrap';
import 'datatables.net-bs/css/dataTables.bootstrap.css';

Then, make sure you configure the css-loader and style-loader right in webpack.config.js file.

How to load the i18n/fonts file asynchronously ?

Extend the dataTable.defaults object

1
2
3
4
5
$.extend(true, $.fn.dataTable.defaults, {
language: {
url: require('../lib/zh_cn.json')
}
});

Loading i18n files is just like loading icon font files.

1
2
3
4
5
6
7
{
test : /\.(json|ttf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
loader : 'file-loader',
query:{
name:'[name]-[md5:hash:8].[ext]'
}
}

How to load extensions ?

Check the extensions list, https://www.npmjs.com/~datatables

To import an extension, normaly it would require two steps as following:

1
2
import 'datatables.net-fixedheader';
import 'datatables.net-fixedheader-bs/css/fixedHeader.bootstrap.css';

1. Load the extension script, normaly the entry file is defined in package.json main property ;

2. Load the style for specific front-end framework. For example, bs means Bootstrap.

How to use string templates

ES6 template is a good choice

Data:

1
2
3
4
5
6
7
8
9
[
{
name:'1',
foo:{
bar:1
}
}
...
]

Columns:

1
2
3
4
5
6
7
8
[
{
data:'foo',
title:'foo.bar',
render:foo=>`<em>${foo.bar}</em>`
}
...
}

How to use React components in cell rendering

Let’s assume we need to use the react-toggle component in our table.

We have three ways to use it:

Implement render function and use React DOM server’s renderToStaticMarkup method

1
2
3
4
5
6
7
{
columns:[{
...
render:elem=>renderToStaticMarkup(<Toggle/>)
}]
...
}

Implement datatable createdCell function and create multiple React roots

1
2
3
4
5
6
7
{
columns:[{
...
createdCell:(td,val)=>render(<Toggle/>,td)
}]
...
}

Prepare the HTML markup first and then initialize the Datatable

1
2
3
4
5
6
7
8
9
10
<tbody>
{
DATA.map(e=><tr key={e.id}>
<td>{e.id}</td>
<td data-order={e.id}>{e.name}</td>
<td>{e.value}</td>
<td><Toggle/></td>
</tr>)
}
</tbody>

A performance test page is written to show the difference between these ways

http://localhost:8080/static/toggle

performance

And here is the performance report with 5000 data in table comparing above three options.

Option Duration usedJSHeapSize
renderToStaticMarkup 6384.74ms 29.75M
render 4579.46ms 103.95M
markup 6497.95ms 189.78M

Summary:

  • If your component is dummy(e.g stateless funtion), use renderToStaticMarkup
  • If your component has state, but has no dependency in its context, use render
  • If your component has state or has dependency in its context (e.g Redux Componet or React Router Links), use markup

TODO

  • work with JSON data
  • Lifecycle
  • Using React in column rendering
  • How to avoid conflicts between them
  • Work with react-dom/server

React 中集成 Datatables

源码

react-zeroclipboard-example

An example showing how to use ZeroClipboard in React application.

Preface

This example assumes you have working experience with these tools or frameworks

  • React
  • Babel
  • Webpack
  • ZeroClipboard

The code base is written in ES6, and we will use Babel to compile it to ES5. Webpack comes in handy to make this workflow automated.

Demo

http://wyvernnot.github.io/react-zeroclipboard-example/

Install ZeroClipboard

ZeroClipboard can be easily installed using NPM.

1
npm install zeroclipboard -D

Import ZeroClipboad

After installing it, it can be imported just like importing other modules.

1
2
import React from 'react';
import ZeroClipboard from 'zeroclipboard';

Create new CopyButton component

We will create a React component using stateless functions,
the button is intialized in the ref-callback

1
2
3
4
5
const CopyButton = (props)=> {
return <button {...props} ref={elem=>new ZeroClipboard(elem)}>
{props.children}
</button>
}

Use the CopyButton component in your app

1
<CopyButton data-clipboard-text={textToBeCopied}>Click to copy</CopyButton>

Solve the SWF dependency

After the package is built and deployed, you will most likely run into this problem.

swf 404 not found

The SWF file is not found, you can mannually copy this file into your static server and make sure the path is correct.
But you need to do this everytime after you do a fresh deployment. Let’s solve the problem using webpack.

First, we need to config the ZeroClipboard’s swfPath option:

1
2
3
ZeroClipboard.config({
swfPath: require('zeroclipboard/dist/ZeroClipboard.swf')
});

This piece of code looks very strange because a .swf file is required. Unless, the second step, we tell webpack how to deal with
.swf files. We need a loader which will simply copy required dependencies which
ends with .swf to the webpack output path. This loader can be installed by typing npm install file-loader -D. Last, we change the
module.loaders part in webpack.config.js to:

1
2
3
4
{
test: /\.swf$/,
loader: 'file-loader'
}

Close

In this example, we build a React application which integrates ZeroClipboard. The application is written in ES6
and compiled to ES5 using webpack. In order to solve the ZeroClipboard.swf 404 not found problem smartly, we use a
webpack loader called file-loader.