问题

最近在项目中遇到这样一个问题,webpack生成的vender包的哈希值在我和同事的电脑上不一致。由于之前已经配置过了CommonsChunkPlugin(配置如下),所以我们期望的结果是在不同环境下构建出的文件哈希值应该是一致的。

注:只给出了和本文内容相关的配置项

module.exports = {
    entry: {
        app: './src/js/app.jsx',
        vender: [
            'classnames',
            'react',
            'react-dom',
            'redux',
            'react-redux',
            'redux-promise-middleware',
            'updeep',
            'axios',
            'jdc-rc-list',
            'react-router',
            'react-router-redux'
        ]
    },
    output: {
        path: path.resolve(__dirname, './build/js/'),
        filename: (isDevelopmentEnvironment ? '[name].bundle.js' : '[name].[chunkhash].js'),
        chunkFilename: (isDevelopmentEnvironment ? '[name].bundle.js' : '[name].[chunkhash].js')
    },
    plugins: [
        new webpack.optimize.CommonsChunkPlugin({
            name: ['vender', 'manifest'],
            minChunks: Infinity,
        })
    ]
};

调查

经过多次验证发现,我们在各自的电脑上多次构建出来的哈希值可以保持一致,只是在两台电脑上不一致。考虑到vender中打包的都是第三方依赖,于是猜测是两台电脑上安装的依赖包不同。接着对比了两人的package.json中的dependencies,完全一致。

考虑到package.json文件中包版本中的^符号匹配的范围比较大。参考npm官方给出的解释

^1.2.3 := >=1.2.3 <2.0.0
^0.2.3 := >=0.2.3 <0.3.0
^0.0.3 := >=0.0.3 <0.0.4

于是怀疑是我俩的环境中安装依赖包的小版本不同。于是使用npm ls命令查看:

npm ls --depth 0 --prod

结果如下:

正是react-redux的版本号不一致。

解决方案

既然知道了问题,那么也就有解决方法了。那就是把项目依赖的包都锁定在某一个固定版本。强制大家安装完全相同的依赖树。经过调查发现以下几种解决方案:

方案一 npm install –save-exact/-E

--save-exact/-E参数强制npm在package.json中写死固定的版本号,而不使用如~^这类的范围符号。比如使用该参数安装left-pad包:

npm install --save -E left-pad

如下图所示,版本号被锁定在1.1.3:

该方案的问题: 只能控制你直接依赖的包(也就是出现在package.json里的那些)。假设你依赖了包A,包A又依赖了包B。那么即使你使用了-E参数安装包A,由于包A内部没有写死包B的版本号,还是有可能得到不一致的依赖树。

因此该方法并不能解决我们的问题。

方案二 npm shrinkwrap

npm shrinkwrap命令会在项目路径下创建一个npm-shrinkwrap.json文件。该文件中包含了当前项目中所有依赖包的版本信息。把该文件提交到git中。其他人在clone该项目执行npm install时,npm检测到该文件后会按照文件中的信息完整的还原出完全相同的依赖树。从而解决版本不一致问题。

具体使用方法如下:

npm install --save-dev left-pad
npm prune
npm shrinkwrap --dev

注意:–dev参数告诉npm shrinkwrap要在输出中包含devDependencies的版本信息。从npm 4.0.1起,不在需要额外指定此参数。

该方法缺陷:安装一个依赖包,原来只需要一条命令,现在却需要三条命令。不方便。

方案三 yarn

yarn是一个与npm兼容的node包管理器。使用它安装npm包,会自动在项目目录中创建一个yarn.lock文件。该文件包含了当前项目中所安装的依赖包的版本信息。其他人在使用yarn安装项目的依赖包时就可以通过该文件创建一个完全相同的依赖环境。

使用方法如下:

yarn # 现有项目中使用yarn来创建yarn.lock
yarn init # 使用yarn新建一个项目
yarn add xxxx # 安装一个依赖包
yarn remove xxx # 删除一个依赖包

更多命令参考yarn官方文档。除了可以自动帮你锁定依赖包的版本之外,yarn还有一个很棒的特性就是可以缓存已经安装过的包。当再次安装时,直接从本地读取即可,可以极大的提高依赖安装速度。

该方法缺陷:需要团队成员都使用yarn,增加初始的沟通成本。

最终选择

综合考虑,最终我们选择基于yarn的解决方案。因为只需要在初期在团队中最好推广、约定,后期就可以无负担的享受yarn带来的版本锁定功能。同时缓存功能也能极大的提高开发体验。可以说是一劳永逸啦!