【Vue】Vue1.0+Webpack1+Gulp项目升级构建方案的踩坑路

最近半年在维护公司的一个管理后台项目,搭建之初的技术栈比较混乱,构建方案采用了Gulp中调用Webpack的方式,Gulp负责处理.html文件,Webpack负责加载.vue.js等。而在这一套构建方案中,主要有这些问题:javascript

  1. 没有实现JS压缩、CSS兼容等功能。
  2. 在开发模式下,保存代码,项目会进行彻底的从新打包,持续构建速度不只缓慢,还会产生缓存的现象(构建完成后刷新页面改动不生效)。
  3. 因为目前的方案没有使用http-proxy-middleware这样的请求代理模块,致使项目在本地开发时还要部署后端服务,对新接手的开发者不友好,并且常常因为沟通不及时产生测试环境与本地环境的代码同步问题。

所以,在熟悉这个项目以后,打算对其构建方案进行升级,主要为了解决上述的问题。css

1. 原有构建方案描述

原有构建速度

  • npm run build:打包约50s
  • npm run dev:开启开发模式约50s,保存自动从新编译需约6s,编译完成后须要刷新才能看到效果,偶尔因缓存问题须要再次自动从新编译才能看到效果

原有构建结果

  • ./build/development:存放渲染后的js文件
  • ./build/html:存放渲染后的html文件
  • ./build/rev:保存各个入口文件hash值的json文件

打包代码解析

/** * 使用gulp-clean插件删除build目录下的文件 */
gulp.task('clean', function () {
    if (!stopClean) {
        return gulp.src('build/' + directory, { read: false }).pipe(clean())
    }
})
/** * 使用webpack打包vue与js文件,在clean以后进行 */
gulp.task('webpack', ['clean'], function (callback) {
    deCompiler.run(function (err, stats) {
        if (err) throw new gutil.PluginError('webpack', err)
        gutil.log('[webpack]', stats.toString({}))
        callback()
    })
})
/** * 使用gulp-uglify插件对js文件进行丑化,在webpack以后进行 */
gulp.task('minify', ['webpack'], function () {
    if (environment) {
        return
    } else {
        return gulp.src('build/' + directory + '/*.js').pipe(uglify())
    }
})
/** * 使用gulp-rev插件为打包后的文件增长hash,在minify以后运行 * * gulp-rev会作什么: * 根据静态资源内容,生成md5签名,打包出来的文件名会加上md5签名,同时生成一个json用来保存文件名路径对应关系。 * 替换html里静态资源的路径为带有md5值的文件路径,这样html才能找到资源路径。 * 有些人可能会作:静态服务器配置静态资源的过时时间为永不过时。 * 达到什么效果: * 静态资源只需请求一次,永久缓存,不会发送协商请求304 * 版本更新只会更新修改的静态资源内容 * 不删除旧版本的静态资源,版本回滚的时候只须要更新html,一样不会增长http请求次数 */
gulp.task('hashJS', ['minify'], function () {
    var dest = gulp.src(['一串入口文件...'])
        .pipe(rev()) // 设置文件的hash key
        .pipe(gulp.dest('build/' + directory)) // 将通过管道处理的文件写出到目录
        .pipe(rev.manifest({})) // 生成映射hash key的json
        .pipe(gulp.dest('build/rev')) // 将通过管道处理的文件写出到目录
    !environment && gulp.src(['一串入口文件...']).pipe(clean())
    return dest
})
/** * 使用gulp-rev-replace插件为html中引用的js和css替换新的hash * 使用gulp-livereload插件在全部文件从新打包完成后局部更新页面 */
gulp.task('revReplace', ['hashJS'], function () {
    return gulp.src(['html/*.html'])
        .pipe(revReplace({ ... })) // 给html中的js引用提供新的hash
        .pipe(gulp.dest('build/html')) // 输出文件
        .pipe(livereload()) // 局部更新页面
})
/** * 使用gulp.watch,当应用程序目录下有任何文件发生改变,则从新执行一遍打包命令 * gulp.watch:监视文件,而且能够在文件发生改动时候作一些事情。 */
gulp.task('watch', ['revReplace'], function () {
    stopClean = true
    livereload.listen()
    gulp.watch('app/**/*', ['clean', 'webpack', 'minify', 'hashJS', 'revReplace'])
})
/** * 输出dev和build的工做流 */
gulp.task('default', ['clean', 'webpack', 'minify', 'hashJS', 'revReplace', 'watch']) // dev
gulp.task('build', ['clean', 'webpack', 'minify', 'hashJS', 'revReplace']) // build
/** * webpack配置 */
var devCompiler = webpack({
    entry: {
        ... // 一众入口文件
        vendor: ['vue', 'vue-router', 'lodash', 'echarts'] // 公共模块
    },
    output: {
        path: ..., // 全部输出文件的目标路径
        publicPath: ..., // 输出解析文件的目录
        filename: ..., // 输出文件
        chunkFilename: ... // 经过异步请求的文件
    },
    // 排除如下内容打包到 bundle,减少文件大小
    external: {
        jquery: 'jQuery',
        dialog: 'dialog'
    },
    plugins: [
        /** * 经过将公共模块拆出来,最终合成的文件可以在最开始的时候加载一次,便存到缓存中供后续使用。 * 这个带来页面速度上的提高,由于浏览器会迅速将公共的代码从缓存中取出来,而不是每次访问一个新页面时,再去加载一个更大的文件。 */
        new webpack.optimize.CommonsChunkPlugin({
            name: ['vendor']
        }),
        /** * DefinePlugin 容许建立一个在编译时能够配置的全局常量。这可能会对开发模式和生产模式的构建容许不一样的行为很是有用。 */
        new webpack.DefinePlugin({
            __VERSION__: new Date().getTime()
        })
    ],
    resolve: {
        root: __dirname,
        extensions: ['', '.js', '.vue', '.json'], // 解析组件的文件后缀白名单
        alias: { ... } // 配置路径别名
    },
    module: {
        // 各个文件的loaders
        loaders: [
            { test: /\.vue$/, loader: 'vue-loader' },
            { test: /\.css$/, loader: 'style-loader!css-loader' },
            { test: /\.jsx$/, loader: 'babel-loader', include: [path.join(__dirname, 'app')], exclude: /core/ },
            { test: /\.json$/, loader: 'json' }
        ]
    },
    vue: {
        loaders: {
            js: 'babel-loader'
        }
    }
})
复制代码

2. 将Gulp的功能移到Webpack1上执行

使用html-webpack-plugin插件构建项目的主.html文件

module.exports = {
    plugins: [
        new HtmlWebpackPlugin({
            filename: '...', // 输出的路径
            template: '...', // 提取源html的路径
            chunks: ['...'], // 须要导入的模块
            inject: true // 是否附加到body底部
        })
    ]
}
复制代码

使用webpack.optimize.UglifyJsPlugin插件进行JS压缩

module.exports = {
    plugins: [
        new webpack.optimize.UglifyJsPlugin({
            compress: { warnings: false }
        })
    ]
}
复制代码

使用webpack-dev-server模块,提供node搭建的开发环境

module.exports = {
    devServer: {
        clientLogLevel: 'warning', // 输出日志的级别,配置为警告级别以上才输出
        inline: true, // 启动 live reload
        hot: true, // 容许启用热重载
        compress: true, // 对全部静态资源进行gzip压缩
        open: true, // 默认在启动本地服务时打开浏览器
        quiet: true, // 禁止输出繁杂的构建日志
        host: ..., // 服务启动的域名
        port: ..., // 服务启动的端口
        proxy: { ... }, // http代理配置
        /** * 这个配置经常使用于解决spa应用h5路由模式下将全部404路由匹配回index.html的问题 * 因为生产环境为主页匹配了一个比较简单的别名,所以开发环境也照搬后端服务的配置 */
        historyApiFallback: {
            rewrites: [{ from: '/^\/admin/', to: '...' }]
        }
    }
}
复制代码

踩坑

  1. webpack-dev-server@3.2.1 requires a peer of webpack^@4.0.0 but none is installed.:这两个模块版本不兼容,回退到webpack-dev-server@2成功运行。
  2. Cannot resolve module 'fsevents' ...:将全局的webpack调用改成直接从node_modules/webpack下直接调用,解决了问题,node node_modules/webpack/bin/webpack.js --config webpack.config.js
  3. Cannot resolve module 'fs' ...:配置config.node.fs = 'empty',为Webpack提供node原生模块,使其能加载到这个对象。
  4. 热重载只对.js.css.vue中的<style>内样式生效,对.vue文件中的html模板及js内容都不生效,会打印“模块代码已发生改变并从新编译,但热重载不生效,可能会启用全局刷新的策略”之类的信息,暂时没有解决,初步判断是低版本的vue-hot-reload-api对这些部分的处理有问题,有大神了解原理能够在评论区科普一哈=.=。

3. 从Webpack1升级到Webpack3

因为Webpack2Webpack3几乎彻底兼容,只是涉及到一些增量的功能,所以选择直接从Webpack1迁移到Webapck3,先在项目中安装Webpack3,而后根据Webpack2文档中《从Webpack1迁移》的章节,对配置项进行更改,参考的文档戳这个:www.html.cn/doc/webpack…html

此次升级没有遇到什么问题,根据文档配置稍做更改就跑通了。梳理一下目前为止实现的功能:前端

  1. 新的Webpack构建代码已经实现了原有的全部功能,下面列举新增的功能。
  2. 使用webpack-dev-server做为开发服务器,实现了保存时live reload的功能。
  3. 使用http-proxy-middleware插件,将请求直接代理到测试服,让开发环境脱离了本地部署的后端服务,大大下降了开发环境部署的时间成本。
  4. 新增friendly-errors-webpack-plugin,输出友好的构建日志,打印几个重要模块的开发环境地址,配置方面彻底参考了vue-cli@2的默认配置。
  5. 新增postcss-loader,对css添加兼容处理,配置方面彻底参考了vue-cli@2的默认配置。
  6. 使用webpack.optimize.UglifyJsPlugin压缩js代码。

尝试进行构建,输出构建时间记录:vue

  • npm run build:约135s
  • npm run dev:初次构建约58s,持续构建约30s

项目构建时间过长(第一次打包把本身吓了一跳...),只能继续寻求构建速度上的优化java

4. 在Webpack3下进行构建速度的优化

使用webpack-jarvis监测构建性能

webpack-jarvis是一个图形化的webpack性能监测工具,它配置简便,对构建过程的时间占比、构建结果的详细记录都有具体的输出node

// 通过简单的配置就能够在本地3001端口输出构建结果记录
const Jarvis = require('webpack-jarvis')
module.exports = {
    plugins: [
        new Jarvis({
            watchOnly: false,
            port: 3001
        })
    ]
}
复制代码

使用happypack

先根据网上搜到的文章,作一些简单的优化,如使用happypack,这个模块经过多进程模型,来加速代码构建,可是使用以后貌似没有太明显的结果,构建时间大概减小了几秒吧...暂时还不太懂这个模块对优化什么场景的效果比较明显,以前有看到一篇讲解happypack原理的文章,但还没细看,有兴趣小伙伴能够研究一下,要是能在评论里简洁明了的给渣渣楼主解释一下就更好了TUT:taobaofed.org/blog/2016/1…jquery

const HappyPack = require('happypack')
const os = require('os')
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length })
module.exports = {
    plugins: [
        new HappyPack({
            // happypack的id,在调用时须要声明,若须要编译其余类型的文件须要再声明一个happypack
            id: 'js',
            // cacheDirectory:设置后,将尽可能在babel编译时使用缓存加载器的结果,避免从新走一遍babel的高昂代价
            use: [{ loader: 'babel-loader', cacheDirectory: true }],
            // 根据cpu的核心数判断须要拆分多少个进程池
            threadPool: happyThreadPool,
            // 是否输出编译过程的日志
            verbose: true
        })
    ]
}
复制代码

作完这一步后,输出构建时间记录:webpack

  • npm run build:约130s
  • npm run dev:初次构建约60s,持续构建约30s

devtool配置为cheap-module-eval-source-map

devtool选项启用cheap-module-eval-source-map模式:vue-cli@2默认配置为这种模式,cheap表明在输出source-map时省略列信息;module表示在编译过程当中启用如babel-loader这样的预编译器,使得调试时能够直接看到未经编译的源代码;eval表示启用eval模式编译,该模式直接使用eval函数执行编译后模块的字符串,减小了将字符串转化为可执行的代码文件这个步骤,加快了项目开发中重建的速度;source-map表示输出源代码的映射表,使得开发时能够直接把错误定位到源代码,提升开发效率。git

作完这一步后,效果并不明显=.=(相比原来的source-map),大概减小了几秒,输出构建时间记录:

  • npm run build:约130s
  • npm run dev:初次构建约58s,持续构建约30s

使用html-webpack-plugin-for-multihtml提高多入口项目重建速度

重建一次居然须要30s!各类搜索找到了html-webpack-plugin的一条issue,发现html-webpack-plugin@2在构建多入口应用时速度确实有明显变慢的状况,缘由是没有成功的对构建内容进行缓存,使每次重建都从新编译全部代码。做者给出的解决方案是使用这个模块的一个分支项目(是由做者本人fork原项目并针对这个问题进行修复的项目)html-webpack-plugin-for-multihtml,用法与html-webpack-plugin彻底相同,使用以后重建仅需1s左右。

作完这一步后,输出构建时间记录:

  • npm run build:约130s
  • npm run dev:初次构建约58s,持续构建约1s

使用webpack.DllPlugin提取公共模块

在输出结果中找到了很多较大的依赖包,如Vue的核心库、lodashecharts等等,还有一些不但愿被打包的静态资源,想办法避免每次都编译这些内容,提高编译速度,因此找到了这个插件。

webpack.DllPlugin这个插件是来源于Windows系统的.dll文件(动态连接库)的用法:首先经过DllPlugin模块构建出一个包含公共模块的包和一个映射表,再经过DllReferencePlugin模块经过映射表给每一个模块关联对应的依赖,这样能够对这些公共模块进行预先打包,之后构建的时候就不须要处理这些模块,减小打包时间。

// webpack.dll.conf.js
const webpack = require('webpack')
module.exports = {
    entry: {
        vendor: [...]
    },
    output: {
        path: resolve('build/development'),
        filename: '[name].dll.js',
        library: '[name]_library'
    },
    plugins: [
        new webpack.optimize.UglifyJsPlugin(),
        new webpack.DllPlugin({
            path: resolve('build/development/[name]-manifest.json'), // 生成manifest文件输出的位置和文件名称
            name: '[name]-library', // 与output.library是同样的,对应manifest.json文件中name字段的值,防止全局变量冲突
            context: __dirname
        })
    ]
}
// webpack.base.conf.js
const webpack = require('webpack')
module.exports = {
    plugins: [
        new webpack.DllReferencePlugin({
            context: __dirname,
            manifest: require('../build/development/vendor-manifest.json') // 让webpack从映射表获取使用的依赖
        })
    ]
}
复制代码

打包出来以后还须要在html文件中引入公共库vendor.dll.js文件

<html>
    <head></head>
    <body>
        <div id="app"></div>
        <script src="/build/development/vendor.dll.js"></script>
        <!-- 其余JS应该注入到dll的后面,确保可以引用到公共库的内容 -->
    </body>
</html>
复制代码

作完这一步后,输出构建时间记录,发现构建效率有了明显的提升:

  • npm run dll:约25s
  • npm run build:约70s
  • npm run dev:初次构建约55s,持续构建约1s

5. 后记

优化到这里就差很少结束,此次的优化为旧项目提供了新一代spa项目应有的一些功能,搭建了更现代的本地开发环境。因为本文篇幅有点太长,完整的配置就丢在另外一篇文章里。

6. Q&A

Q: 为何不直接升级到Webpack4

A: Webpack4只支持vue-loader@15以上版本,而这个版本已经没法解析Vue1的文件。

做者信息

做者其余文章

相关文章
相关标签/搜索