关于Android工程师转成vue的三两事儿(4)--webpack
最近为了能够写一份值得参考的webpack文档,特意的去查了好多相应的书籍,博客。距离上次写的那篇文章好想也过去将近一周的时间了。我想是时候要准备下一篇文章了。不然就食言而肥了。 算了,技术类文章就直接从技术类开始说起吧。首先,学习webpack呢?是因为我在开发vue和react的时候遇到了这个工具,然后最近在看人家的招聘要求的时候,总是会带上这个。
然后我就趁着下班时间研究了一下这个东西。还是和之前的说法一样,如果有任何的疑惑,请在留言区留言,如果我能看见一定会及时的向您反馈。 新弄的github代码地址 ###1、新语言的诞生背景 近些年来web应用的功能需求的完善和所设计到领域越发宽广导致前端萌生了很多新的思想和框架。我简单的介绍一下: 首先行业的领导着提出模块化的思想,他们认为将一个复杂的系统分为多个模块来开发会大大减少开发难度和提升开发效率。同时考虑到css只能用静态的语法描述元素的样式,无法像写javascript那样增加逻辑判断与共享变量。于是在这种大环境下,诞生了es6、typescript、scss等新的语言。但是考虑到例如es6无法在浏览器中直接运行,需要将es6转换成es5之后浏览器才能识别。于是新的构建工具就出现了。 构建工具的功能主要是:进行代码转换、文件压缩、代码分割、模块合并、自动刷新、代码检验、自动发布等功能。构建其实是工程化、自动化思想在前端开发的体验,我们要做的其实是用代码去让前端项目自动化的执行这一系列化复杂的流程罢了。 说到构建工具,我看百度上有很多。例如npm script、grunt、gulp、fis3、webpack等等。因为本文主要是讲webpack,如果有机会能接触到以上的几个工具,我会另外详细的描述。 ###2、 webpack的优势 从我这些天无论是自己的实践还是书上写的来说,我认为webpack本身就是特别符合模块化开发的一种工具。在webpack里面一切文件都是模块,并通过loader转换文件,通过plugin注入钩子,最后输出由多模块组合成的文件。其优点主要是: 1、 专注于处理模块化开发的项目,能做到开箱即用,一步到位 2、 能通过plugin扩展 3、 应用各种领域 4、 社区庞大 5、 良好的开发体验 但谈及为什么要选用webpack,我看书上主要有以下几个看法: 1、大多数团队在开发新项目的时候都会采用紧跟时代的技术,这些技术基本都会采用“模块化+新语言+新框架”,webpack可以为这些新项目提供一站式的解决方案 2、webpack有良好的生态链和维护团队,能提供一定的开发体验并保证质量 3、webpack被全世界大量的web开发者使用和验证,能找到各个层次面所需的教程和经验分享 (反正,综上所述 嗯 你再不学webpack就out了?) ###3、 webpack的安装(需要node环境滴)
初始化项目
如上图所示,新建了一个项目。
npm init 复制代码
全局安装
// 最新版本好像变成了webpack-cli注意一下 npm install -g webpack 复制代码
项目内安装
npm install webpack --save-dev 复制代码
在这里扯一句闲话,可能很多教程谈到安装webpack都会让你选择直接-g,但是我并不推荐你这么做,我总感觉-g之后就成了全局变量,但是我并不是每个项目内都能用到这个所谓的webpack,将webpack的作用域设置为项目内,功能与全局没有差别。 ###4、webpack的基础使用 如下图所示,在上面新建的项目下面新建index.js.。然后敲一些简单的js代码:
然后打包该js
// 打包代码
webpack-cli index.js --output build/bundle.js --mode development
复制代码
打包成功之后就会发现目录下面多了一个build目录,里面有bundle.js文件,最后新建index,html,并引用该js,就会看到如下的效果
但是看到这个效果我觉得并不是很满意,因为我想要这句话成为一个红色的。此时就要用到css了。随即调用最后进行打包的时候发现会报如下的错误根据其解释应该是少了一个loader导致打包失败,最后导入css-loader、style.css之后,并且按照官方的文档指示,成功加载出想要的页面但此时的问题又来了,我每次在进行操作的时候都需要去加载一个css,每次这样子的引用就会使得代码变得臃肿就完全没又体现webpack的核心优势了。于是在查阅资料得知原来webpack和很多的框架一样都能进行只能配置,然后我就按照官网的指示一步步的进行配置这样做的好处也是体现到前面关于webpack的自动化配置、编译的体现。但是我们发现每次编译之后,都需要去打包、刷新页面这样做实在是太浪费时间了,这里就要谈到webpack-dev-server。老规矩首先安装
cnpm install webpack-dev-server --save
复制代码
然后在控制台里面输入下面代码,然后在网页中打开http://localhost:8080/发现就能直接将代码运行在网页上面
// 两步
webpack-cli
webpack-dev-server
复制代码
但是虽然是看到了本地编译的html被弄上了去,但是我们发现每次都需要编译文件之后再进行webpack-dev-server插件弄到网上去,但是我每当js改变之后 并没能够自动编译,挺麻烦的。于是我便做了如下操作 但是此时的问题又来了,对于一个初学者,谁会记得这么长,这么麻烦的代码呢?在Android里面一般这种东西都会用到一个配置文件写好,然后每次都直接使用就完事儿了。说道配置文件,这里就想到了webpack.json。于是就在script标签下面添加如下代码,也能达到刚刚的效果 同样我们也可以把dev-server放置在webpack.config.js文件中,更多的配置项呢?烦请诸位看客直接去阅读官方文档 说到这里呢?我们虽然讲js和css打包了,但是并没有打包html文件。查阅一番知道webpack里面有一个插件是专门用来打包的:
cnpm install html-webpack-plugin --save
复制代码
然后加入配置项
但是说到这么多依然没有提到我们前面说的新语言的引用啊,前面在说优势的时候都说了webpack支持es6=》es5 啊,此时就不能不提到babel了。如图安装babel插件,然后写一些es6的语法看看以上就是我无论是看其他书籍,还是博客外加上阅读官网上得来的一些经验。虽然做的东西比较简单,但是这都无疑体现出了webpack的优良的品质。如果您对以上内容存在有任何的疑虑或者是有任何指教,欢迎提出 我会第一时间与您交流、讨论。 好了,webpack的入门相信大家看到这里都已经入门了,接下来我就结合一下具体的vue实际的例子来和大家进行讨论。 ###5、vue中的webpack 我在这里利用的就是我用vue-cli脚手架新生成的一个vue项目,就结合我这段时间的一些所见所闻来做一个简单的赘述:
- index.js
'use strict' // 严格模式
// Template version: 1.2.7
// see http://vuejs-templates.github.io/webpack for documentation.
const config = require('./config') // 导入config文件
const path = require('path') //使用Node自带的文件路径插件module.exports = {// 开发环境dev: {// PathsassetsSubDirectory: 'static', // 编译输出的二级目录assetsPublicPath: '/', // 编译发布上线路径的根目录,可配置为资源服务器域名或 CDN 域名proxyTable: {}, // 需要 proxyTable 代理的接口(可跨域),详情请看之前的文章// Various Dev Server settingshost: '0.0.0.0', // host,如果设置成0.0.0.0可以通过统一局域网其他设备通过ip访问该网页port: 8080, // 网页默认端口号,如果端口被占用会自动分配一个随即未被占有的端口autoOpenBrowser: false, // 是否自动打开浏览器errorOverlay: true, // 在浏览器是否展示错误蒙层notifyOnErrors: true, // 是否展示错误的通知// 这个是webpack-dev-servr的watchOptions的一个选项,指定webpack检查文件的方式// 因为webpack使用文件系统去获取文件改变的通知。在有些情况下,这个可能不起作用。例如,当使用NFC的时候,// vagrant也会在这方面存在很多问题,在这些情况下,使用poll选项(以轮询的方式去检查文件是否改变)可以设定为true// 或者具体的数值,指定文件查询的具体周期。poll: false,// Use Eslint Loader?useEslint: true,// eslint代码检查showEslintErrorsInOverlay: false, // 如果设置为true,在浏览器中,eslint的错误和警告会以蒙层的方式展现。/*** Source Maps*/// https://webpack.js.org/configuration/devtool/#developmentdevtool: 'eval-source-map', // 调试工具cacheBusting: true, // 指定是否通过在文件名称后面添加一个查询字符串来创建source map的缓存cssSourceMap: false, // 是否开启 cssSourceMap},// 正式环境build: {// Template for index.htmlindex: path.resolve(__dirname, '../dist/index.html'), // 编译注入的 index.html 文件,必须是本地的绝对路径// PathsassetsRoot: path.resolve(__dirname, '../dist'), // 编译输出的静态资源根路径assetsSubDirectory: 'static', // 编译输出的二级目录assetsPublicPath: config.getOutPutPath(), // 编译发布上线路径的根目录,可配置为资源服务器域名或 CDN 域名// assetsPublicPath: './',/*** Source Maps*/productionSourceMap: true, //生成用于生产构建的源映射devtool: '#source-map', // 调试代码的模式,共有7种,这里是生成source-map文件productionGzip: false, // 是否开启 gzipproductionGzipExtensions: ['js', 'css'], // 需要使用 gzip 压缩的文件扩展名// 一个实用工具,用于分析项目的依赖关系// 如果这个选项是true的话,那么则会在build后,会在浏览器中生成一份bundler报告bundleAnalyzerReport: process.env.npm_config_report}
}
复制代码
- utils.js
const path = require('path') // 引入nodejs的path模块,用于操作路径
const config = require('../config') // 引入模板的配置文件,下面就需要去这个文件中看看有什么基本的配置
const ExtractTextPlugin = require('extract-text-webpack-plugin') // 提取特定文件的插件,比如把css文件提取到一个文件中去
const packageConfig = require('../package.json') // 加载package.json文件// 生成编译输出的二级目录
exports.assetsPath = function (_path) {const assetsSubDirectory = process.env.NODE_ENV === 'production'? config.build.assetsSubDirectory: config.dev.assetsSubDirectory// path.posix是path模块跨平台的实现(不同平台的路径表示是不一样的)return path.posix.join(assetsSubDirectory, _path)
}// 为不同的css预处理器提供一个统一的生成方式,也就是统一处理各种css类型的打包问题。
// 这个是为在vue文件中的style中使用的css类型
exports.cssLoaders = function (options) {options = options || {}// 打包css模块const cssLoader = {loader: 'css-loader',options: {sourceMap: options.sourceMap}}// 编译postcss模块const postcssLoader = {// 使用postcss-loader来打包postcss模块loader: 'postcss-loader',// 配置source mapoptions: {sourceMap: options.sourceMap}}// 创建loader加载器字符串,结合extract text插件使用/**** loader:loader的名称* loaderOptions:loader对应的options配置对象*/function generateLoaders (loader, loaderOptions) {// 通过usePostCSS 来标明是否使用了postcssconst loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]// 如果指定了具体的loader的名称if (loader) {// 向loaders的数组中添加该loader对应的加载器// 一个很重要的地方就是,一个数组中的loader加载器,是从右向左执行的。loaders.push({// loader加载器的名称loader: loader + '-loader',// 对应的加载器的配置对象options: Object.assign({}, loaderOptions, {sourceMap: options.sourceMap})})}// 如果明确指定了需要提取静态文件,则使用// ExtractTextPlugin.extract({})来包裹我们的各种css处理器。if (options.extract) {return ExtractTextPlugin.extract({use: loaders,// fallback这个选项我们可以这样理解// webpack默认会按照loaders中的加载器从右向左调用编译各种css类型文件。如果一切顺利,在loaders中的// 各个加载器运行结束之后就会把css文件导入到规定的文件中去,如果不顺利,则继续使用vue-style-loader来处理// css文件fallback: 'vue-style-loader'})} else {// 如果没有提取行为,则最后再使用vue-style-loader处理cssreturn ['vue-style-loader'].concat(loaders)}}// https://vue-loader.vuejs.org/en/configurations/extract-css.htmlreturn {css: generateLoaders(),postcss: generateLoaders(),less: generateLoaders('less'),sass: generateLoaders('sass', { indentedSyntax: true }),scss: generateLoaders('sass'),stylus: generateLoaders('stylus'),styl: generateLoaders('stylus')}
}// 使用这个函数,为那些独立的style文件创建加载器配置。
exports.styleLoaders = function (options) {// 保存加载器配置的变量const output = []// 获取所有css文件类型的loadersconst loaders = exports.cssLoaders(options)for (const extension in loaders) {const loader = loaders[extension]// 生成对应的loader配置output.push({test: new RegExp('\\.' + extension + '$'),use: loader})}return output
}exports.createNotifierCallback = () => {// node-notifier是一个跨平台的包,以类似浏览器的通知的形式展示信息。const notifier = require('node-notifier')return (severity, errors) => {// 只展示错误的信息if (severity !== 'error') returnconst error = errors[0]const filename = error.file && error.file.split('!').pop()// 需要展示的错误信息的内容notifier.notify({// 通知的标题title: packageConfig.name,// 通知的主体内容message: severity + ': ' + error.name,// 副标题subtitle: filename || '',// 通知展示的iconicon: path.join(__dirname, 'logo.png')})}
}
复制代码
- vue.loader.config.js
const utils = require('./utils')
const config = require('../config')
// 设置是不是生产环境
const isProduction = process.env.NODE_ENV === 'production'
// 根据不同的环境,引入不同的source map配置文件
const sourceMapEnabled = isProduction? config.build.productionSourceMap: config.dev.cssSourceMapmodule.exports = {// vue文件中的css loader配置loaders: utils.cssLoaders({sourceMap: sourceMapEnabled,// 生产环境下就会把css文件抽取到一个独立的文件中extract: isProduction}),// css source map文件的配置cssSourceMap: sourceMapEnabled,// css source map文件缓存控制变量cacheBusting: config.dev.cacheBusting,transformToRequire: {video: ['src', 'poster'],source: 'src',img: 'src',image: 'xlink:href'}
}
复制代码
- build/webpack.base.conf.js
const path = require('path') // 使用 NodeJS 自带的文件路径插件
const utils = require('./utils') //封装了一些方法的工具
const config = require('../config') //使用 config/index.js
const vueLoaderConfig = require('./vue-loader.conf') //使用vue-loader.conffunction resolve (dir) {return path.join(__dirname, '..', dir) // 拼接我们的工作区路径为一个绝对路径
}
// eslint的规则
const createLintingRule = () => ({// 对.js和.vue结尾的文件进行eslint检查test: /\.(js|vue)$/,// 使用eslint-loaderloader: 'eslint-loader',// enforce的值可能是pre和post。其中pre有点和webpack@1中的preLoader配置含义相似。// post和v1中的postLoader配置含义相似。表示loader的调用时机// 这里表示在调用其他loader之前需要先调用这个规则进行代码风格的检查enforce: 'pre',// 需要进行eslint检查的文件的目录存在的地方include: [resolve('src'), resolve('test')],// eslint-loader配置过程中需要指定的选项options: {// 文件风格的检查的格式化程序,这里使用的是第三方的eslint-friendly-formatterformatter: require('eslint-friendly-formatter'),// 是否需要eslint输出警告信息emitWarning: !config.dev.showEslintErrorsInOverlay}
})
var webpack = require('webpack')
module.exports = {// webpack在寻找寻找相对路径的文件时候会以context作为根目录。// context默认为执行启动webpack时所在的当前工作目录context: path.resolve(__dirname, '../'),// entry表示入口,webpack构建的第一步从entry开始// 类型可以是string,object,arrayentry: {app: './src/main.js'},output: {// 输出文件存放的目录,必须是string类型的绝对目录path: config.build.assetsRoot,// 通过entry不同生成不同的文件名字,详情请看文章filename: '[name].js',// 发布到线上所有资源的url前缀,为string类型publicPath: process.env.NODE_ENV === 'production'? config.build.assetsPublicPath: config.dev.assetsPublicPath,// 导出库的名称,为string类型,不填写的时候,默认输出格式是匿名的立即执行函数// library: "KlivitamLibrary"// 导出库的类型,为枚举类型,默认是var// 可选值: umd umd2 commonjs2,commonjs,amd,this,var,assign,window,global,jsonp// libraryTarget: "jsonp"// 是否包含游泳的文件信息到生成的代码里// pathinfo: true// 附加chunk的文件名称// chunkFilename: "[id].js"// chunkFilename: "[chunkhash].js"// jsonp异步加载资源时的回调函数名// jsonpFunction: "webpackJsonP"// 生成source map文件的名称// sourceMapFilename: "[file].map"//浏览器开发者工具里显示的远吗模块名称// devtoolModuleFilenameTemplate: "webpack:///[resource-path]"// 异步加载跨域的资源时使用的方式// crossOriginLoading: "use-credentials",// crossOriginLoading: "anonymous",// crossOriginLoading: false,},// 配置模块解析时候的一些选项resolve: {// 自动补全的扩展名,能够使用户在引入模块时不带扩展extensions: ['.js', '.vue', '.json'],// 默认路径代理,例如 import Vue from 'vue$',会自动到 'vue/dist/vue.esm.js'中寻找alias: {'vue$': 'vue/dist/vue.esm.js','@': resolve('src'),'components': resolve('src/components'),// 可以在引入文件的时候使用pages符号引入src/pages文件夹中的文件'pages': resolve('src/pages'),'http': resolve('src/http'),'public': resolve('src/public'),'jquery': 'jquery'}},// 下面是针对具体的模块进行的具体的配置// 下面的配置语法采用的是version >= @2的版本module: {noParse: [/videojs-contrib-hls/], // 不用解析和处理的模块// rules是一个数组,其中的每一个元素都是一个对象,这个对象是针对具体类型的文件进行的配置。rules: [...(config.dev.useEslint ? [createLintingRule()] : []),{test: /\.vue$/, // 正则匹配loader名字loader: 'vue-loader', // loader名字// 针对此加载器的具体配置// 针对前面的分析,这个配置对象中包含了各种css类型文件的配置,css source map的配置 以及一些transform的配置options: vueLoaderConfig},{test: /\.js$/,// js文件的处理主要使用的是babel-loader。在这里没有指定具体的编译规则,babel-loader会自动// 读取根目录下面的.babelrc中的babel配置用于编译js文件loader: 'babel-loader',// 指定需要进行编译的文件的路径// 这里表示只对src和test文件夹中的文件进行编译include: [resolve('src'), resolve('test'), resolve('config/myapi')] //规则所包含的文件夹},{// 对图片资源进行编译的配置// 指定文件的类型test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,// 使用url-loader进行文件资源的编译loader: 'url-loader',// url-loader的配置选项options: {// 文件的大小小于10000字节(10kb)的时候会返回一个dataUrllimit: 10000,// 生成的文件的保存路径和后缀名称name: utils.assetsPath('img/[name].[hash:7].[ext]')}},{ // 对视频进行打包编译test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,loader: 'url-loader',options: {limit: 10000,name: utils.assetsPath('media/[name].[hash:7].[ext]')}},{ // 对字体进行打包编译test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,loader: 'url-loader',options: {limit: 10000,name: utils.assetsPath('fonts/[name].[hash:7].[ext]')}}]},// 这些选项用于配置polyfill或mock某些node.js全局变量和模块。// 这可以使最初为nodejs编写的代码可以在浏览器端运行node: {// 这个配置是一个对象,其中的每个属性都是nodejs全局变量或模块的名称setImmediate: false,// prevent webpack from injecting mocks to Node native modules// that does not make sense for the client// 设置成empty则表示提供一个空对象dgram: 'empty',fs: 'empty',net: 'empty',tls: 'empty',child_process: 'empty'}
}
复制代码
+build/webpack.dev.conf.js
'use strict'
// 首先引入的是一些工具方法,下面我们就需要去util文件种看一下有哪些对应的工具方法
const utils = require('./utils')
// 引入webpack模块
const webpack = require('webpack')
// 引入配置文件
// 这个配置文件中包含了一些dev和production环境的基本配置
const config = require('../config')
// 引入webpack-merge模块。这个模块用于把多个webpack配置合并成一个配置,后面的配置会覆盖前面的配置。
const merge = require('webpack-merge')
// 引入webpack的基本设置,这个设置文件包含了开发环境和生产环境的一些公共配置
const baseWebpackConfig = require('./webpack.base.conf')
// 用于生成html文件的插件
const HtmlWebpackPlugin = require('html-webpack-plugin')
// 这个插件能够更好的在终端看到webpack运行时的错误和警告等信息。可以提升开发体验
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
// 查找一个未使用的端口
const portfinder = require('portfinder')// 获取host环境变量,用于配置开发环境域名
const HOST = process.env.HOST
// 获取post环境变量,用于配置开发环境时候的端口号
const PORT = process.env.PORT && Number(process.env.PORT)// 开发环境的完整的配置文件
const devWebpackConfig = merge(baseWebpackConfig, {module: {// 为那些独立的css类型文件添加loader配置(没有写在vue文件的style标签中的样式)rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })},// 开发环境使用'eval-source-map'模式的source map// 因为速度快devtool: config.dev.devtool,// 下面是对webpack-dev-server选项的基本配置,这些配置信息,我们可以在/config/index.js// 文件中进行自定义配置。devServer: {// 用于配置在开发工具的控制台中显示的日志级别// 注意这个不是对bundle的错误和警告的配置,而是对它生成之前的消息的配置clientLogLevel: 'warning',// 表示当使用html5的history api的时候,任意的404响应都需要被替代为index.htmlhistoryApiFallback: true,// 启用webpack的热替换特性hot: true,// 一切服务都需要使用gzip压缩// 可以在js,css等文件的response header中发现有Content-Encoding:gzip响应头compress: true,// 指定使用一个 host。默认是 localhost// 如果希望服务器外部可以访问(通过我们电脑的ip地址和端口号访问我们的应用)// 可以指定0.0.0.0,使用这个可以使得同一局域网内所有设备都能访问你的本地网页host: HOST || config.dev.host,// 指定要监听请求的端口号port: PORT || config.dev.port,// 是否自动打开浏览器open: config.dev.autoOpenBrowser,// 当编译出现错误的时候,是否希望在浏览器中展示一个全屏的蒙层来展示错误信息overlay: config.dev.errorOverlay// 表示只显示错误信息而不显示警告信息// 如果两者都希望显示,则把这两项都设置为true? { warnings: false, errors: true }// 设置为false则表示啥都不显示: false,// 指定webpack-dev-server的根目录,这个目录下的所有的文件都是能直接通过浏览器访问的// 推荐和output.publicPath设置为一致publicPath: config.dev.assetsPublicPath,// 配置代理,这样我们就可以跨域访问某些接口// 我们访问的接口,如果符合这个选项的配置,就会通过代理服务器转发我们的请求proxy: config.dev.proxyTable,// 启用 quiet 后,除了初始启动信息之外的任何内容都不会被打印到控制台。这也意味着来自 webpack 的错误或警告在控制台不可见。quiet: true, // necessary for FriendlyErrorsPlugin// 与监视文件相关的控制选项watchOptions: {// 如果这个选项为true,会以轮询的方式检查我们的文件的变动,效率不好poll: config.dev.poll,}},plugins: [// 创建一个在编译时可以配置的全局变量new webpack.DefinePlugin({'process.env': require('../config/dev.env')}),// 启用热替换模块// 记住,我们永远不要再生产环境中使用hmrnew webpack.HotModuleReplacementPlugin(),// 这个插件的主要作用就是在热加载的时候直接返回更新文件的名称,而不是文件的idnew webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.// 使用这个插件可以在编译出错的时候来跳过输出阶段,这样可以确保输出资源不会包含错误。new webpack.NoEmitOnErrorsPlugin(),// 这个插件主要是生成一个html文件new HtmlWebpackPlugin({// 生成的html文件的名称filename: 'index.html',// 使用的模板的名称template: 'index.html',// 将所有的静态文件都插入到body文件的末尾inject: true}),]
})module.exports = new Promise((resolve, reject) => {portfinder.basePort = process.env.PORT || config.dev.port// 这种获取port的方式会返回一个promiseportfinder.getPort((err, port) => {if (err) {reject(err)} else {// 把获取到的端口号设置为环境变量PORT的值process.env.PORT = port// 重新设置webpack-dev-server的端口的值devWebpackConfig.devServer.port = port// 将FriendlyErrorsPlugin添加到webpack的配置文件中devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({// 编译成功时候的输出信息compilationSuccessInfo: {messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],},// 当编译出错的时候,根据config.dev.notifyOnErrors来确定是否需要在桌面右上角显示错误通知框onErrors: config.dev.notifyOnErrors? utils.createNotifierCallback(): undefined}))// resolve我们的配置文件resolve(devWebpackConfig)}})
})
复制代码
- webpack.prod.conf.js
// 引入path模块
const path = require('path')
// 引入工具方法
const utils = require('./utils')
// 引入webpack模块
const webpack = require('webpack')
// 引入基本的配置
const config = require('../config')
// 引入webpack-merge模块
const merge = require('webpack-merge')
// 引入开发环境和生产环境公共的配置
const baseWebpackConfig = require('./webpack.base.conf')
// 这个模块主要用于在webpack中拷贝文件和文件夹
const CopyWebpackPlugin = require('copy-webpack-plugin')
// 这个插件主要是用于基于模版生成html文件的
const HtmlWebpackPlugin = require('html-webpack-plugin')
// 这个插件主要是用于将入口中所有的chunk,移到独立的分离的css文件中
const ExtractTextPlugin = require('extract-text-webpack-plugin')
// 这个插件主要是用于压缩css模块的
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
// 这个插件主要是用于压缩js文件的
// const UglifyJsPlugin = require('uglifyjs-webpack-plugin')// 配置多份环境
let env
if(process.argv[2] === 'test'){env=require('../config/dev.env')
}else{env=require('../config/prod.env')
}// 合并公共配置和生产环境独有的配置并返回一个用于生产环境的webpack配置文件
const webpackConfig = merge(baseWebpackConfig, {// 用于生产环境的一些loader配置module: {rules: utils.styleLoaders({sourceMap: config.build.productionSourceMap,// 在生产环境中使用extract选项,这样就会把thunk中的css代码抽离到一份独立的css文件中去extract: true,usePostCSS: true})},// 配置生产环境中使用的source map的形式。在这里,生产环境使用的是#source map的形式devtool: config.build.productionSourceMap ? config.build.devtool : false,output: {// build所产生的文件的存放的文件夹地址path: config.build.assetsRoot,// build之后的文件的名称// 这里[name]和[chunkhash]都是占位符// 其中[name]指的就是模块的名称// [chunkhash]chunk内容的hash字符串,长度为20filename: utils.assetsPath('js/[name].[chunkhash].js'),// [id]也是一个占位符,表示的是模块标识符(module identifier)chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')},plugins: [// http://vuejs.github.io/vue-loader/en/workflow/production.htmlnew webpack.DefinePlugin({'process.env': env}),// 压缩javascript的插件new webpack.optimize.UglifyJsPlugin({// 压缩js的时候的一些基本配置uglifyOptions: {// 配置压缩的行为compress: {// 在删除未使用的变量等时,显示警告信息,默认就是falsewarnings: false}},// 使用 source map 将错误信息的位置映射到模块(这会减慢编译的速度)// 而且这里不能使用cheap-source-mapsourceMap: config.build.productionSourceMap,// 使用多进程并行运行和文件缓存来提高构建速度parallel: true}),// 提取css文件到一个独立的文件中去new ExtractTextPlugin({// 提取之后css文件存放的地方// 其中[name]和[contenthash]都是占位符// [name]就是指模块的名称// [contenthash]根据提取文件的内容生成的 hashfilename: utils.assetsPath('css/[name].[contenthash].css'),// 从所有额外的 chunk(additional chunk) 提取css内容// (默认情况下,它仅从初始chunk(initial chunk) 中提取)// 当使用 CommonsChunkPlugin 并且在公共 chunk 中有提取的 chunk(来自ExtractTextPlugin.extract)时// 这个选项需要设置为trueallChunks: true,}),// duplicated CSS from different components can be deduped.// 使用这个插件压缩css,主要是因为,对于不同组件中相同的css可以剔除一部分new OptimizeCSSPlugin({// 这个选项的所有配置都会传递给cssProcessor// cssProcessor使用这些选项决定压缩的行为cssProcessorOptions: config.build.productionSourceMap? { safe: true, map: { inline: false } }: { safe: true }}),// 创建一个html文件new HtmlWebpackPlugin({// 创建一个html文件filename: config.build.index,// 使用的模板的名称template: 'index.html',// 把script和link标签放在body底部inject: true,// 配置html的压缩行为minify: {// 移除注释removeComments: true,// 去除空格和换行collapseWhitespace: true,// 尽可能移除属性中的引号和空属性removeAttributeQuotes: true// more options:// https://github.com/kangax/html-minifier#options-quick-reference},// chunks 目录chunks: ['manifest', 'vendor', 'app'],// 控制chunks的顺序,这里表示按照依赖关系进行排序// 也可以是一个函数,自己定义排序规则chunksSortMode: 'dependency'}),// 根据模块的相对路径生成一个四位数的hash作为模块idnew webpack.HashedModuleIdsPlugin(),// webpack2处理过的每一个模块都会使用一个函数进行包裹// 这样会带来一个问题:降低浏览器中JS执行效率,这主要是闭包函数降低了JS引擎解析速度。// webpack3中,通过下面这个插件就能够将一些有联系的模块,// 放到一个闭包函数里面去,通过减少闭包函数数量从而加快JS的执行速度。new webpack.optimize.ModuleConcatenationPlugin(),// 这个插件用于提取多入口chunk的公共模块// 通过将公共模块提取出来之后,最终合成的文件能够在最开始的时候加载一次// 然后缓存起来供后续使用,这会带来速度上的提升。new webpack.optimize.CommonsChunkPlugin({// 这是 common chunk 的名称name: 'vendor',// 把所有从mnode_modules中引入的文件提取到vendor中minChunks (module) {// any required modules inside node_modules are extracted to vendorreturn (module.resource &&/\.js$/.test(module.resource) &&module.resource.indexOf(path.join(__dirname, '../node_modules')) === 0)}}),// 为了将项目中的第三方依赖代码抽离出来,官方文档上推荐使用这个插件,当我们在项目里实际使用之后,// 发现一旦更改了 app.js 内的代码,vendor.js 的 hash 也会改变,那么下次上线时,// 用户仍然需要重新下载 vendor.js 与 app.js——这样就失去了缓存的意义了。所以第二次new就是解决这个问题的// 参考:https://github.com/DDFE/DDFE-blog/issues/10new webpack.optimize.CommonsChunkPlugin({name: 'manifest',minChunks: Infinity}),// This instance extracts shared chunks from code splitted chunks and bundles them// in a separate chunk, similar to the vendor chunk// see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunknew webpack.optimize.CommonsChunkPlugin({name: 'app',async: 'vendor-async',children: true,minChunks: 3}),// copy custom static assets// 拷贝静态资源到build文件夹中new CopyWebpackPlugin([{// 定义要拷贝的资源的源目录from: path.resolve(__dirname, '../static'),// 定义要拷贝的资源的目标目录to: config.build.assetsSubDirectory,// 忽略拷贝指定的文件,可以使用模糊匹配ignore: ['.*']}])]
})if (config.build.productionGzip) {// 如果开启了生产环境的gzipconst CompressionWebpackPlugin = require('compression-webpack-plugin')webpackConfig.plugins.push(new CompressionWebpackPlugin({// 目标资源的名称// [path]会被替换成原资源路径// [query]会被替换成原查询字符串asset: '[path].gz[query]',// gzip算法// 这个选项可以配置成zlib模块中的各个算法// 也可以是(buffer, cb) => cb(buffer)algorithm: 'gzip',// 处理所有匹配此正则表达式的资源test: new RegExp('\\.(' +config.build.productionGzipExtensions.join('|') +')$'),// 只处理比这个值大的资源threshold: 10240,// 只有压缩率比这个值小的资源才会被处理minRatio: 0.8}))
}if (config.build.bundleAnalyzerReport) {// 如果需要生成一分bundle报告,则需要使用下面的这个插件const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPluginwebpackConfig.plugins.push(new BundleAnalyzerPlugin())
}
复制代码
- build/check-versions.js
// 在终端为不同字体显示不同的风格
const chalk = require('chalk')
// 解析npm包的version
const semver = require('semver')
// 引入package.json文件
const packageConfig = require('../package.json')
// node版本的uninx shell命令
const shell = require('shelljs')
// 执行命令的函数
function exec (cmd) {return require('child_process').execSync(cmd).toString().trim()
}const versionRequirements = [{name: 'node',// node的版本// process.version就是node的版本// semver.clean('v8.8.0') => 8.8.0currentVersion: semver.clean(process.version),// package.json中定义的node版本的范围versionRequirement: packageConfig.engines.node}
]// 相当于 which npm
if (shell.which('npm')) {// 如果npm命令存在的话versionRequirements.push({name: 'npm',// 检查npm的版本currentVersion: exec('npm --version'),// package.json中定义的npm版本versionRequirement: packageConfig.engines.npm})
}module.exports = function () {const warnings = []for (let i = 0; i < versionRequirements.length; i++) {const mod = versionRequirements[i]// semver.satisfies()进行版本之间的比较if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {// 如果现有的npm或者node的版本比定义的版本低,则生成一段警告warnings.push(mod.name + ': ' +chalk.red(mod.currentVersion) + ' should be ' +chalk.green(mod.versionRequirement))}}if (warnings.length) {console.log('')console.log(chalk.yellow('To use this template, you must update following to modules:'))console.log()for (let i = 0; i < warnings.length; i++) {const warning = warnings[i]console.log(' ' + warning)}console.log()process.exit(1)}
}
复制代码
- build/build.js
'use strict'
require('./check-versions')() // 检查npm和node的版本process.env.NODE_ENV = 'production' // 设置环境变量NODE_ENV的值是productionconst ora = require('ora') // 终端的spinner
const rm = require('rimraf') // node.js版本的rm -rf
const path = require('path') // 使用Node自带的文件路径工具
const chalk = require('chalk') // 引入显示终端颜色模块
// const opn = require('opn') // 一个可以强制打开浏览器并跳转到指定url的插件
const webpack = require('webpack') // 使用webpack
const config = require('../config') // 加载config的配置
const webpackConfig = require('./webpack.prod.conf') // 引入webpack在production环境下的配置文件const spinner = ora('building for production...')
spinner.start()// 删除打包目标目录下的文件
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {if (err) throw errwebpack(webpackConfig, (err, stats) => {// 进行打包spinner.stop()if (err) throw err// 输出打包的状态process.stdout.write(stats.toString({colors: true,modules: false,children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.chunks: false,chunkModules: false}) + '\n\n')// 如果打包出现错误if (stats.hasErrors()) {console.log(chalk.red(' Build failed with errors.\n'))// 退出process.exit(1)}console.log(chalk.cyan(' Build complete.\n'))console.log(chalk.yellow(' Tip: built files are meant to be served over an HTTP server.\n' +' Opening index.html over file:// won\'t work.\n'))})
})
复制代码
###6、最后说两句
本来想想还有很多东西要写的,预备是从最基础的方面说起,但是一想到可能会有很多东西要写。外加上我就我自己而言,我都无法去看那么长的博客,何况和我一样浮躁的程序员呢?并且,我在这个上面已经花了将近二周的时间了。自己其实也差不多知道webpack再重要也不过是js的一个附着品,自己就算学的再精湛,也只不过是景上添花,但是处于js快速增长期的我 不应该把大把的时间花在这个上面。总之综合考虑,我决定将webpack分为两个章节:基础篇和深入篇。
好了,基础篇到这里就结束了,今天从下班开始一直整理到9点多。直接在项目文件上面做的标注,不知道老大看到了会不会打我。不管了 万一是躲不过就git reset???
转载于:https://juejin.im/post/5cdd45706fb9a0323a01db3f
关于Android工程师转成vue的三两事儿(4)--webpack相关推荐
- 关于Android 工程师转成vue的三两事儿(2)--前端开发技巧
前面的文章也提到了,我本身就是做android的,外加上刚开始做android的时候.学长对我的代码风格有很大的限制.所以我在学习最新的语言的时候,首先会想到的是代码的格式化.虽然说vue-cl ...
- 关于Android工程师转vue的三两事儿(10)--原型与原型链
说起原型和原型链接,着实让我这个前端菜鸟胡搞了好一阵子.虽然有点绕口的缘故,但是更多的还是自己比较浮躁带来的后果,这一块据说是前端的基础,看了很多遍才差不多有点头目.分享一下我领悟到的武林秘籍,希望能 ...
- 成为android工程师,应届生如何在三年内成为Android开发高级工程师?
谢谢邀请,作为一个在软件行业混了十几年的码农,从正常的经验来讲要成为一个行业的高级工程师,需要五年的开发经验,一般情况将三年培养一个标准的工程师,三年要成为一个高级工程师几乎是之前速度的两倍,要达到这 ...
- 【CSDN软件工程师能力认证学习精选】vue.js 三种方式安装(vue-cli)
CSDN软件工程师能力认证(以下简称C系列认证)是由中国软件开发者网CSDN制定并推出的一个能力认证标准.C系列认证历经近一年的实际线下调研.考察.迭代.测试,并梳理出软件工程师开发过程中所需的各项技 ...
- 闭关一个月,吃透三百页pdf,终于拿下腾讯Android工程师offer!
前言 为什么要尽量让自己进大厂? 如果毕业就进了大厂,那你将得到业内大牛的指导,以及随处可见的技术碰撞.新技术的跟进也是非常快的,在这样的环境中,你的技术成长自然是非常快的.如果自己足够努力,用不了三 ...
- Android录屏并利用FFmpeg转换成gif(三) 在Android中使用ffmpeg命令
Android录屏并利用FFmpeg转换成gif(三) 写博客时经常会希望用一段动画来演示app的行为,目前大多数的做法是在电脑上开模拟器,然后用gif录制软件录制模拟器屏幕,对于非开发人员来讲这种方 ...
- android 视频画面切割,抖音三屏黑白特效在哪里?安卓手机画面分割器将视频画面分割成黑白三屏的方法...
注意此教程方案是『安卓手机端教程方案』 如果在手机端操作不方便或对眼睛不好 也可以用另外电脑端的教程方案操作:抖音一屏变三屏视频[找更多方案] 今天要介绍的画面分割器,并不是解决同一点呢屏幕左右分屏的 ...
- Android工程师面试该怎么准备?年薪50W
都说Android最近行情不好,很多人都遇到瓶颈或放弃或转行.其实这种情况17年18年也是如此,相对比之下,个人认为今年比去年好多了,Android接下来将会走向复苏的春天. 自从Google开始推出 ...
- android工程师 腾讯,腾讯音乐Android工程师一面面试题记录,拿走不谢!
最近参加了一次鹅厂音乐Android工程师面试,这里凭记忆记录了一些一面的面试题,希望能帮到正在面试的你! 1.Java调用函数传入实际参数时,是值传递还是引用传递? 2.单例模式的DCL方式,为什么 ...
最新文章
- java 反射遍历_java使用反射遍历类的字段
- 定义国际贸易术语(Incoterms)
- CyclicBarrier-同步辅助类
- Bioconda软件安装神器:多版本并存、环境复制、环境导出
- python opencv库下载_PythonopenCV 2.4.3 cv2.SolvePnP
- c语言 函数 收集,c语言库函数大全--资料收集+
- 如何在硅谷一夜暴富?
- 关于C#窗体程序dataGridView控件的用法
- layui富文本编辑器
- 计算机考研复试汇总(所有科目)
- 博途编程语言切换_从一种编程语言切换到另一种:灵活的好处
- 简单系统U盘制作 / 安装系统
- meterpreter使用
- 第九周 练习判断闰年和平年
- 【嵌入式模块】OLED显示屏模块
- 单臂路由之一,单网口软路由实现主路由功能,光猫或交换机剩余网口实现上网功能
- 请问怎么用python画一个轮廓图,,例如中国地图这种,注意是轮廓图,求大佬指点
- Setup Factory打包注册dll
- 【蓝桥杯】最长子序列
- show master status 时没有数据显示
热门文章
- nodejs中的模块的理解
- Linux磁盘分区之fdisk命令
- VMware 安装Ubuntu 无法进入安装界面
- 在多模块开发的时候,利用项目继承可以将结构信息、部署信息,将共同的依赖放在一个父类中。...
- 多文件上传组件FineUploader使用心得
- CSU 1115: 最短的名字(字典树)
- VB.NET 使用DirectSound9 (3) StreamAudio
- 重磅|前浪、后浪 一起迎接风口! BCS 2020向全球发起议题征集
- 聊聊网络安全行业这十年(2010-2019)
- python之路--小数据池,再谈编码,is和 == 的区别