creeperyang / blog

前端博客,关注基础知识和性能优化。
MIT License
2.63k stars 211 forks source link

前端工具(util)或自动化(workflow)的问题 #13

Closed creeperyang closed 7 years ago

creeperyang commented 8 years ago

这里纪录了前端工具使用和工作流的问题,包括但不限于编译打包,版本管理,代码测试,工程化等等。

关键词:Babel Webpack git等等。

强调下,除非特殊说明,否则这里的babel, babel-cli都是6.x版本的。

creeperyang commented 8 years ago

1. babel@6.xrequire加载模块的问题[babel-noderequire

问题详细描述:

"scripts": {
  "clean": "babel-node --eval \"require('./tools/clean')().catch(err => console.log(err.stack))\""
}

npm run clean

require('./tools/clean')().catch(function (err) {
                        ^
TypeError: object is not a function

'./tools/clean'.js的内容:

const clean = async () => {
    await del(['build/*', '!build/.git'], { dot: true });
    await fs.mkdir('build');
};
export default clean;

比较奇怪,报错信息竟然是 require('./tools/clean') 不是函数。我们改下npm run clean的内容,直接输出 require('./tools/clean')

{ __esModule: true, default: [Function] }

OK,似乎比较清晰了: require('./tools/clean') 返回的我们不是需要的clean函数,而是包裹一层的{default: clean function}

那么为什么babel-noderequire不是直接输出export default的值呢?

  1. export default x会被babel编译为exports.default = x
  2. 默认require时加载到的就是{ __esModule: true, default: x},所以出现上述错误;
  3. babel编译import x from 'x'会处理成(0, _x2.default),可以正确获取default

既然如此,尝试改为"clean": "babel-node --eval \"import clean from './tools/clean'; clean().catch(err => console.log(err.stack))\"",报错:

SyntaxError: [eval]: Modules aren't supported in the REPL
> 1 | import clean from './tools/clean'; clean().catch(err => console.log(err.stack))

好吧,看来我们只能手动去获取default再执行了。

2. babel@6.x的编译配置问题

问题背景:babel编译koa插件时希望保留generator不要编译。

"presets": ["es2015"]

然而preset中并不能单独disable某个plugin,详见https://phabricator.babeljs.io/T3016。

因此比较挫/比较快速的一个变通就是手动去配置每个plugin。

creeperyang commented 8 years ago

3. react-transform-hmrstate变动不更新问题

缘起:更新上面所说的React Hot Loading已经过时了,开发者也宣布已经停止维护,现在有一个更强大的babel plugin: React Transform来代替他...

想紧跟时代,用高大上的react-transform,然后,倒在了state的问题上。详见https://github.com/gaearon/react-transform-hmr/issues/41

然后我傻傻地去改this.state,满心期望transform能够成功。然而,浏览器log一切正常,页面内容却始终不变...一次,两次,三次,换个姿势再来,嗯,就是不起作用——这让我一度怀疑人生,为什么按作者的一步步来,我就是不成功。

google之,没找到答案,试验了千万种道路,最后只能一步一步调试,到最后发现react-proxy这个库更新得到新的ReactComponent时,并没有去复制最新的state。

What the hell! 当我满怀悲愤地准备提issue时,发现不仅有了提了相同的问题,甚至已经9天前有人提了pr修复这个问题,更加泪流满面 :sob: 。

一个深刻的道理:搜索真的是一门技术,先到github项目上搜,搜issue请把:is open去掉。最后的最后,才是自己源码调试...

另:为什么我一上来碰到的就是corner case,发现以上答案后,我偷偷改了改render函数,哇,真的浏览器无刷新更新了诶!!!

creeperyang commented 8 years ago

4. [react-transform-catch-errors]()的not work的问题

场景:配置好react-transform-catch-errors后,特意在render中加一行<h2>error。满心欢喜等待redbox来个大大的错误显示,结果是在控制台输出错误并重刷页面了。

心情不好了。吸取上面的教训,直接到repo到issues去找找看。功夫不负有心人,十几分钟后找到了答案:https://github.com/gaearon/react-transform-catch-errors/issues/17

很简单,语法错误不被webpack-dev-server支持。

What you want is unrelated to this transform.

Syntax error overlay is a feature of a different project called webpack-hot-middleware. It's a replacement for webpack-dev-server.

creeperyang commented 8 years ago

5. Babel转换async函数的参数问题

问题的核心是async函数转换后没有正确处理默认参数。代码如下:

const mergeImage = async (sourceImgPaths, mergedImgPath, arrangement = 'smart', options = {}) => {
    console.log(sourceImgPaths, mergedImgPath, arrangement, options);
}

转换后:

var mergeImage = function () {
    var ref = _asyncToGenerator(regeneratorRuntime.mark(function _callee(sourceImgPaths, mergedImgPath) {
        var arrangement = _arguments.length <= 2 || _arguments[2] === undefined ? 'smart' : _arguments[2];
        var options = _arguments.length <= 3 || _arguments[3] === undefined ? {} : _arguments[3];
        var margin, images, rects, pack, mergedImage, batch;
        return regeneratorRuntime.wrap(function _callee$(_context) {
        // ...

很明显,arrangementoptions的转换完全错误(正确的应该是类似var arrangement = arguments.length <= 2)。

查看下_arguments到底什么东西?

function(module, exports, __webpack_require__) {

    'use strict';

    Object.defineProperty(exports, "__esModule", {
        value: true
    });
    exports.mergeImage = undefined;
    var _arguments = arguments;
    // ...
    var mergeImage = function () {

:joy: :joy: :joy:

至此,arrangementoptions总是取不到正确值的原因已找到,但更深入一步,Babel为什么这么转换需要继续查找。

目前环境:

    "babel-cli": "^6.5.1",
    "babel-core": "^6.5.1",
    "babel-loader": "^6.2.2",
    "babel-preset-es2015": "^6.3.13",
    "babel-preset-stage-0": "^6.3.13",

暂时解决方法:

asyncarrow function不混用时,babel是可以正确处理的。

async function mergeImage() {
}
creeperyang commented 8 years ago

6. istanbul + jasminebabel-node测试代码无法收集 coverage information

istanbul之前用的并不太多,现在配合babel-node,jasmine做覆盖测试,各种问题就来了。

执行"test-cov": "node_modules/.bin/babel-node node_modules/istanbul/lib/cli cover test/run.js",输出如下:

Transformation error; return original code
{ [Error: Line 239: Unexpected token] lineNumber: 239, description: 'Unexpected token', index: 7963 }
Transformation error; return original code
{ [Error: Line 1: Unexpected token] lineNumber: 1, description: 'Unexpected token', index: 15 }
...
...
...
Executed 18 of 18 specs SUCCESS in 8 secs.
No coverage information was collected, exit without writing coverage information
  1. 报错Transformation error(但单元测试是正确执行完);
  2. 没有coverage information

针对1并没有找到什么好的处理方法,跟踪istanbul,程序执行到Module.runMain(cmd, null, true);时依然正常。

针对2https://github.com/gotwarlost/istanbul/issues/262 里讨论比较多,然后各种尝试,最后感谢https://github.com/suhasdeshpande/calendar-library/commit/6720509dddfa0192650283648d7917bb46b335a5,更新istanbul1.x(目前还在alpha阶段,但作为测试工具,现在就可以用起)版本即可解决。

creeperyang commented 8 years ago

7. webpack中sass-loader报错Invalid CSS after "xxxx": expected 1 selector or at-rule

真实情况下,报错详细信息:

Module build failed: 
.inject-shadow {
^
      Invalid CSS after "...load the styles": expected 1 selector or at-rule, was "content = requi..."
      in /Users/creeper/work/projects/FlightBooking/app/pages/index/inject.scss (line 1, column 1)
 @ ./app/pages/index/inject.scss 4:14-776 13:2-17:4 14:20-782

错误信息是指无效的css代码,然而检查过后,inject.scss里显然都是合法的css代码,也必然是合法的scss代码了。

sass-loader repo中找到一个类似的issue https://github.com/jtangelder/sass-loader/issues/187,虽然报错信息类似,但该issue中,文件代码是sass格式的,不是合法的scss,报错信息是比较好理解的

对照我的问题,再次check我的loader,有个思路:是不是被sass-loader接受的并不是inject.scss原本的内容,所以有非法代码?

const loaders = [{
    test: new RegExp(MODULE_VIEWER_SETTING.injectStyleName + '$'),
    loader: 'css!postcss-loader!sass-loader'
}, {
    test: /\.scss$/,
    exclude: [/app\/styles\/\S+\.scss$/],
    loader: sassLoader
},

以上是相关的loader定义。loader的本意是scss文件用通用的sassLoader处理,但inject.scss特殊处理。一想,是不是inject.scss被处理了两次,第一次产出的代码不再是合法的scss-loader输入?

测试,把第二个loader的exclude中显式排除inject.scss,果然可以正确运行。

creeperyang commented 8 years ago

8. git commit的迷之报错

git add .
git commit -m 'message'

sh: standard: command not found

git commit失败,给出的原因是sh: standard: command not found。完全找不到思路,google之,依然没思路。

看信息是sh的锅,但完全没道理。尝试git commit -h,可以正常显示help。

最后,在lyn老司机提示下,才发现是commit hook的锅。commit hook会执行standard,然而我并没有装这个依赖...

creeperyang commented 8 years ago

9. css-loader怎么在file-loader/url-loader的帮助下处理静态资源?

一个常见的场景:怎么只让css中的url正确映射图片而不去重新生成新的图片?

// source
static/
    css/a.css (url('../images/x.png'))
    images/x.png

// dest
static/
    css-compiled/a.css (url('../images/x.png'))
    images/x.png

默认情况下,css-loader配合file-loader/url-loader会在static/css-compiled下生成对应的x-hash.png,并且改变css为url(x-hash.png)

怎么配置来达成题目的要求?

file-loader@0.9新支持了emitFile=false选项来禁止生成新的资源文件。同时,配置file-loader/url-loader的name来配合。

                {
                    test: /\.(gif|png|jpe?g)$/i,
                    loader: [
                        'file?emitFile=false&name=../images/[name].[ext]',
                        'url?limit=1000&name=../images/[name].[ext]'
                    ].join('!')
                },
                {
                    test: /\.(ttf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
                    loader : 'file?emitFile=false&name=../fonts/[name].[ext]'
                },
                {
                    test: /\.css$/,
                    loader: ExtractTextPlugin.extract('css-loader!postcss-loader')
                }

参考资料:

  1. https://github.com/webpack/webpack/issues/1370
  2. https://github.com/webpack/css-loader/issues/1
creeperyang commented 8 years ago

10. babel-preset-es2015并不包含Object.assign

在某vivo手机上碰到一个bug,查看报错信息是undefined is not a function—— 很经典的报错。

把报错行数周围的代码看一遍,并没有哪里不对。

       const titleErrMsg = value && verifyTitle(value)
        this.setState({
            deliver: Object.assign({}, this.state.delivery, {
                invList: this.state.delivery.invList.map((inv, i) => {
                    // xxxx
                })
            })
        })

而这段代码可疑之处只有verifyTitle Array.prototype.map Object.assign这3个函数,一一测试,最后发现Object.assignundefined

查看webpack配置,用了es2015, react的配置,查看编译后代码,的确还保留着Object.assign...


正确的配置:

               {
                    test: /\.jsx?$/,
                    exclude: /(node_modules|bower_components)/,
                    loader: 'babel',
                    query: {
                        presets: ['react','es2015'],
                        plugins: ['transform-object-assign']
                    }
                }
creeperyang commented 7 years ago

了解Babel 6生态

一个babel的简介,帮助理清整个Babel 6生态。

内容已转移到 #25

参考:

Clearing up the Babel 6 Ecosystem 6.0.0 Released

creeperyang commented 7 years ago

11. webpack@3 警告 There are multiple modules with names that only differ in casing.

WARNING in ./node_modules/react-toolbox/lib/Link/index.js
There are multiple modules with names that only differ in casing.
This can lead to unexpected behavior when compiling on a filesystem with other case-semantic.
Use equal casing. Compare these module identifiers:
* /Users/creeper/work/waves/lottie_admin/node_modules/react-toolbox/lib/Link/index.js
    Used by 1 module(s), i. e.
    /Users/creeper/work/waves/lottie_admin/node_modules/babel-loader/lib/index.js??ref--0!/Users/creeper/work/waves/lottie_admin/src/components/Header/index.js
* /Users/creeper/work/waves/lottie_admin/node_modules/react-toolbox/lib/link/index.js
    Used by 1 module(s), i. e.
    /Users/creeper/work/waves/lottie_admin/node_modules/react-toolbox/lib/navigation/index.js

经过排查,引起问题的为这样一行代码:

import Link from 'react-toolbox/lib/Link'

但实际目录是这样的:node_modules/react-toolbox/lib/link,所以请注意大小写。