webpack-3x-到webpack-4x-踩坑记录

从jspang的《Webpack3.X版 成神之路》webpack入门(http://jspang.com/posts/2017/...)如今升级到4x,菜鸟独自踩坑一把辛酸泪 1.安装webpackwebpack 4x webpack和webpack-cli要分开安装,不再一次安装只在本地项目安装了webpack和webpack-cli npm install webpack --save-devnpm install webpack-cli --save-dev查看webpack版本时用npx webpack -v想一步到位,最好再全局安装一次 npm install webpack -gnpm install webpack-cli -g2.Webpack打包命令更改webpack 3x打包命令 webpack {entry file} {destination for bundled file}{entery file}:入口文件的路径,本文中就是src/entery.js的路径;{destination for bundled file}:填写打包后存放的路径。 webpack 4x打包命令更加严格严格区分开发与生产环境,mode可以指定 production 或 development,不指定默认为 production。 webpack {entry file} --output-filename {destination for bundled file} --output-path --mode development简写:webpack {entry file} -o {destination for bundled file} --mode development 同样我们可以在package.json里配置,简化命令 "dev": "webpack --mode development","build": "webpack --mode production"

July 12, 2019 · 1 min · jiezi

webpack loader和plugin

适用webpack3。loaderloader主要用于预处理源文件,类似于构建工具中的任务概念开始一段简单的loader编写编写一段js代码,如function loader (source) { var self = this; return source.replace(/<div>/, function (str) { var value = convertStr(self.resource) return str.substring(0, str.length - 1) + ’ data-source="’ + value.substring(value.indexOf(‘src’)) + ‘">’ })}function convertStr (str) { return str.replace(/\/g, function (v) { return ‘/’ })}module.exports = loader;其中source返回的是源文件内容或者上一个loader返回的内容。没错,loader可以有多个,并且按照从后往前的顺序执行。this是webpack的实例,this.resource获得当前的文件地址。在webpack配置文件中,配置resolveLoader: { modules: [ path.resolve(__dirname, ‘../build/rules’), ’node_modules’ ] },因为是本地自制的loader,需要声明地址。默认会直接寻找node_modules中的loader。{ test: /.vue$/, use: [ { loader: ‘vue-loader’, options: vueLoaderConfig }, { loader: ‘vue-source-loader’ } ] },和vue-loader一起监听.vue文件。vue-source-loader是第一个执行的loaderpluginplugin主要完成loader无法完成的事情plugin的主要执行apply方法, 在plugin中存在很多hook钩子,即生命周期阅读webpack 所有的HOOK一个小栗子(完成后效果和上面的loader一致,但是十分复杂且不利于阅读,仅供了解,因为是修改打包后的文件,定有不足之处)function MixBase () {}MixBase.prototype.apply = function (compiler) { compiler.plugin(’emit’, function (compilation, callback) { var arr = new Set() compilation.chunks.forEach(function (chunk) { chunk.modules.forEach(function (module) { module.fileDependencies.forEach(function (filepath) { if (/.vue$/.test(filepath)) { arr.add(filepath) } }) }) chunk.files.forEach(function (filename) { if (/.js$/.test(filename)) { var source = compilation.assets[filename].source(), name = source.match(/name:['|"]['|"]/g), isApp = false, finalSource, componentsArr = [] if (name) { for (var i = 0; i < name.length; i++) { var item = name[i].match(/name:['|"]['|"]/)[1] if (item === ‘App’) isApp = true componentsArr.push(item) } if (isApp) { finalSource = source.replace(/;return [a-z|A-Z](["|'];/, function (v) { return v.replace(/attrs:{id:}/, function (k) { return k.substring(0, k.length - 1) + ‘,“data-source”:“src/app.vue”}’ }) }) } else { finalSource = source.replace(/;return [a-z|A-Z](["|'],/, function (v) { var newStr for (var k of arr) { if (k.indexOf(componentsArr[0]) !== -1) { newStr = k.replace(/\/g, function (str) { return ‘/’ }) newStr = newStr.substring(newStr.indexOf(‘src’)) } } return v + ‘{attrs:{“data-source”:"’ + newStr + ‘"}},’ }) for (var j = 1; j < componentsArr.length; j++) { if (componentsArr[j].trim()) { var reg = new RegExp(,[a-z|A-Z]\\([\\"|\\']${componentsArr[j].toLocaleLowerCase()}\\)) console.log(,[a-z|A-Z]\\([\\"|\\']${componentsArr[j].toLocaleLowerCase()}\\)) finalSource = finalSource.replace(reg, function (v) { var newStr for (var k of arr) { if (k.indexOf(componentsArr[j]) !== -1) { newStr = k.replace(/\/g, function (str) { return ‘/’ }) newStr = newStr.substring(newStr.indexOf(‘src’)) } } return v.substring(0, v.length - 1) + ‘,{attrs:{“data-source”:"’ + newStr + ‘"}})’ }) } } } compilation.assets[filename] = { source: function () { return finalSource }, size: function () { return finalSource.length } } } } }) }) callback() })}module.exports = MixBase ...

March 13, 2019 · 2 min · jiezi

构建多页面应用——单个页面的处理

在看这篇文章之前,需要你对构建多页面应用有一定的基础认识,如果没有的话,可以先参考这篇文章webpack 构建多页面应用。多页面应用是由一个个独立的页面组成。因此,细粒度的处理一个个单页面是构建单页面框架之后的一个重要实现。因为所涵盖的知识点较碎,所以就不按照页面的位置结合组成元素来讲,如:head, body, script等。这里主要介绍head。因为script操作其实就是上一篇文章中已经介绍过的js操作,而body因为内容较多,需要另起一篇文章。页面的头部在上一篇文章中,我们讲述了如何用html-webpack-plugin 生成一个html文件,其中使用了两个配置项chunks,filename,前者指代页面所要引入的js模块,也就是我们常见的html页面中的<script src="…"></script>形式,后者指代文件的名字。那么,在这一部分,要说的就是如何给不同的页面配置生成不同的页面<head>…</head>。我们都知道页面头部包括title、link/style、meta、script 这四部分组成,尤其前三者居多。当然,在web前端开发中js很强大,我们可以用js直接控制,在不同页面的入口js文件中写相应的js代码。这种方法虽然可行,但维护起来比较麻烦,当你修改的时候,你需要查找一个个页面。相对来讲,使用html-webpack-plugin提供的配置项,会使你的开发工作变得简单起来。html-webpack-plugin 插件的配置项title 选项可以为页面指定名字,meta 选项可以为页面指定html文档关联信息,如:描述,作者等,favicon 可以为页面添加一个小图标。 修改 webpack.config.js,代码如下:…nnew HtmlWebapckPlugin({ /* inital page / filename: ‘index.html’, chunks: [‘index’], / page head */ title: ‘index’, meta: { ‘description’: ‘这是首页’, ‘keywords’: ‘webpack, multi-page, 首页’, ‘author’: ‘https://github.com/lvzhenbang/ }, favicon: ‘./assets/19884132.jpg’})…这样头部常用的三个元素我们已经解决了两个。那么接下来就是解决link这个元素的。注:有一个比较特殊的就是html页面图标<link rel=“shortcut icon” href=“19884132.jpg”> ,我们使用 html-webpack-plugin 插件的 favicon 选项已经解决。link 和 style 部分的处理这两个元素常常被用来处理样式。link 处理外部样式,style 处理内联样式。注:很多人会误解,或曲解,这里的样式处理是这样的:在定义的页面入口文件,或者页面入口文件引用的文件中,引入css文件,webapck会将这些样式以内联的形式或者link的形式注入到生成的html页面中。这样我们的应用的目录结构就变成如下这样(本片文章使用如下的目录结构,它也介绍了各个js文件对css文件的引用):├── src │ ├── common // 公用的模块 │ │ ├── a.js // 引用了a.css │ │ ├── b.js // 引用了b.css │ │ ├── c.js // 引用了c.css │ │ ├── d.js ├── assets // 静态资源 │ ├── 19224132.jpg // 用来做页面图标 │ ├── css │ │ ├── a.css │ │ ├── b.css │ │ ├── c.css │ │ ├── main.css │ │ ├── abutus.css │ ├── uttils // 工具 │ │ ├── load.js // 工具代码load.js │ ├── index.js // 主模块index.js (包含a.js, b.js, c.js, d.js),引用了main.css │ ├── aboutUs.js // 主模块aboutus.js (包含a.js, b.js),引用了main.css, aboutus.css │ ├── contactUs.js // 主模块contactus.js (包含a.js, c.js),引用了main.css ├── webpack.config.js // css js 和图片资源 ├── package.json ├── yarn.lock处理为内联样式如果是webpack3.x 推荐使用 css-loader,style-loader,extract-text-webpack-plugin;如果是webapck4.x推荐使用的 css-loader, mini-css-extract-plugin。webpack3.x与webapck4.x都一样,修改webpack.config.js如下:…module: { rules: [ { test: /.css$/, use: [ ‘style-loader’, ‘css-loader’ ] } ]},…因为mini-css-extract-plugin是专门为webpack4.x设计的,如果webapck3.x使用它会报错。处理为外部链接(link)webpack3.x中webpack.config.js修改如下:…const ExtractTextPlugin = require(’extract-text-webapck-plugin’)… module: { rules: [ { test: /.css$/, use: ExtractTextPlugin.extract({ fallback: ‘style-loader’, use: ‘css-loader’ }) } ] }, plugins: [ … new ExtractTextPlugin({ filename: ‘[name].css’ }) ]webpack4.x中webpack.config.js修改如下:…const MiniCssExtractPlugin = require(‘mini-css-extract-plugin’)… module: { rules: [ { test: /.css$/, use: [ MiniCssExtractPlugin.loader, ‘css-loader’ ] } ] }, plugins: [ … MiniCssExtractPlugin() ],从js文件中分离出css文件,webpack3.x借助于extract-text-webpack-plugin,webpack4.x借助于mini-css-extract-plugin,前者给不同的css代码块命名需要在extract-text-webapck-plugin的示例中配置,它使用的是内置的CommonsChunkPlugin插件的拆分原则,后者不需要配置分离css代码块的名字选项,后者借助于SplitsChunkPlugin插件的拆分原则。所以,它们的分离形式与js代码块一致。webpack3.x为每个入口点生成了一个css文件,并提取了它们的公共代码生成了一个新的css文件;webapck4.x为每个入口生成了一个css文件,并提取并生成了这些文件相互之间的公共文件(它和前者不同,后者更精细化,只要是某一个或者几个文件有公共代码就提取出来,然后生成新的文件)。为什么将css文件和js文件分的这么细?是因为这样可以显著的减小首次加载页面时请求文件的大小(lazyload),但是这样做会增加HTTP的请求次数。在多页面应用的过程中,有的人喜欢将所有的css放在一个或两个文件中,而不是像本文中那样为每个页面生成一个css文件,包括它们之间的共用文件。但在多页面应用中,这样精密的细分也有其好处。相对来说,使用CommonsChunkPlugin拆分的css模块更合理些,而使用SplitsChunkPlugin拆分的css模块,则过于细化。至于如何取舍,还需要根据实际情况来定。当然,这里面还有一些小的问题需要优化,后期我会视情况来写相应的文章描述。源代码webpack3.x multi-pagewebpack4.x multi-page构建多页面应用系列文章webpack 构建多页面应用——初探构建多页面应用——单个页面的处理 ...

December 18, 2018 · 2 min · jiezi

webpack 构建多页面应用

如何使用webpack构建多页面应用,这是一个我一直在想和解决的问题。网上也给出了很多的例子,很多想法。猛一看,觉得有那么点儿意思,但仔细看也就那样。使用webpack这个构建工具,可以使我们少考虑很多的问题。我们常见的单页面应用只有一个页面,它考虑问题,解决问题围绕着中心化去解决,因此很多麻烦都迎刃而解。如果你使用过vue.js,那么想必你一定用过vue-router,vuex,它们就是典型的中心化管理模式,当然还有很多,这里不一一列举了。而多页面应用,我们不能再按照中心化模式的路走了,因为行不通,这也是很多人认为多页面应用不好做,或者干脆认为webapck只能做单页面应用,而不能做多页面应用的原因。所以,我要说明的第一点儿是:不要用做单页面应用的思维来做多页面应用。单页面中的模块共享和多页面的模块共享的区别单页面的模块共享,其实是代码块在同一个页面的不同位置的重复出现;而多页面应用的代码块儿共享需要实现的不仅是同一个页面的共享,还要做到跨页面的共享。所以,第一个要解决的问题是:不同页面的代码块共享如何实现?单页面的路由管理,其实是根据用户的触发条件来实现不同的代码块的显隐;而多页面应用的路由管理则不然,它实现的是页面的跳转。所以,第二个要解决的问题是:所页面应用的导航该如何做?单页面的状态管理,很受开发者喜好。单页面是一个页面,所以页面中的数据状态的管理操作起来还算得心应手,那么多页面应用的呢,显然依靠它自身很难实现。所以,第三个要解决的问题是:多页面应用的状态管理如何做?注:这个问题问的其实有点儿傻,如果你做的是dom操作的多页面儿应用,就不用做状态管理了。如果你还是使用想vue.js这样的库,你就需要考虑要不要再用做多页面的状态管理了,因为此法儿就是为单页面应用做的,多页面儿行不通。多页面应用的探索入口(entry):webpack对入口不仅可以定义单个文件,也可以定义多个文件。熟悉当页面应用开发的对于下面的代码应该不会陌生吧?module.exports = { entry: ‘./src/index.js’, ···}我第一次接触真正的单页面应用项目使用的就是angualrjs,使用的构建工具使webapck+gulp,其中的webpack.config.js 中的看到的入口文件代码就是它。后来,接触到的是数组形式,代码如下:module.exports = { entry: [’./src/index.js’, ‘bootstrap’] ···}这样,将bootstrap和入口文件一起引用,就可以在任何一个代码块中使用boostrap。再后来,接触到的是对象形式,代码如下:module.exports = { main: ‘./src/index.js’ ···}这样做的目的是为了给输出的文件指定特定的名字。再后来,就是做多页面应用,就需要用到如下的代码:module.exports = { entry: { index: ‘./src/index.js’, aboutUs: ‘./src/aboutus.js’, contactUs: ‘./src/contactus.js’ }}为了引入第三方库,我们可以像如下这样做:module.exports = { entry: { index: [’./src/index.js’, ’loadsh’], aboutUs: ‘./src/aboutus.js’, contactUs: [’./src/contactus.js’, ’lodash’] }}webpack3.x的探索但为了共享模块代码,我们需要像下面这这样做:const CommonsChunkPlugin = require(‘webpack’).optimization.CommonsChunkPluginmodule.exports = { entry: { index: [’./src/index.js’, ‘./src/utils/load.js’, ’loadsh’], aboutUs: [’./src/aboutus.js’, ’loadsh’], contactUs: [’./src/contactus.js’,’./src/utils/load.js’, ’lodash’] }, plugins: [ new CommonsChunkPlugin({ name: “commons”, filename: “commons.js”, chunks: [“index”, “aboutUs”, “contactUs”] }) ]}这样型就会形成如下所示的项目目录结构:├── src │ ├── common // 公用的模块 │ │ ├── a.js │ │ ├── b.js │ │ ├── c.js │ │ ├── d.js │ ├── uttils // 工具 │ │ ├── load.js // 工具代码load.js │ ├── index.js // 主模块index.js (包含a.js, b.js, c.js, d.js) │ ├── aboutUs.js // 主模块aboutus.js (包含a.js, b.js) │ ├── contactUs.js // 主模块contactus.js (包含a.js, c.js) ├── webpack.config.js // css js 和图片资源 ├── package.json ├── yarn.lock但是这个内置插件的局限性比较大。正如上面所使用的那样,它只会提取chunks选项所匹配的模块共有的代码块。就如同上面代码表示的那样,它只会提取pindex, aboutUs, contactUs共有的代码块loadsh,而不会提取index, contactUs共有的代码块load.js。当然,一般的第三方库,我们也不这样使用,而是像下面这样使用:const CommonsChunkPlugin = require(‘webpack’).optimization.CommonsChunkPluginmodule.exports = { entry: { index: [’./src/index.js’, ‘./src/utils/load.js’], aboutUs: [’./src/aboutus.js’], contactUs: [’./src/contactus.js’,’./src/utils/load.js’], vendors: [’lodash’] }, externals: { commonjs: “lodash”, root: “_” }, plugins: [ new CommonsChunkPlugin({ name: “commons”, filename: “commons.js”, chunks: [“index”, “aboutUs”, “contactUs”] }) ]}对于web应用最终的目的是:匹配生成不同的html页面。这里我们要使用的就是html-webpack-plugin。首先,需要安装html-webpack-plugin:yarn add –dev html-webpack-plugin然后引入插件,并配置如下:…const HtmlWebapckPlugin = require(‘html-webpack-plugin’);… plugins: [ … new HtmlWebapckPlugin({ filename: ‘index.html’, chunks: [‘vendors’, ‘commons’, ‘index’] }), new HtmlWebapckPlugin({ filename: ‘aboutUs.html’, chunks: [‘vendors’, ‘commons’, ‘aboutUs’] }), new HtmlWebapckPlugin({ filename: ‘contactUs.html’, chunks: [‘commons’, ‘contactUs’] }) ], …这样一个基于webpack3.x的多页面框架就有了基本的样子。webpack4.x的探索而使用webpack4.x则完全不同,它移除了内置的CommonsChunkPlugin插件,引入了SplitChunksPlugin插件,这个插件满足了我们的需要,弥补了CommonsChunkPlugin的不足。如果你想要解决之前的不足,去提取index, contacUs共有的模块,操作起来会很简单。正如上面的所列举的那样,我们有三个入口点index, aboutUs, contactUs,SplitChunksPlugin 插件会首先获取这三个入口点共有的代码块,然后建立一个文件,紧接着获取每两个入口点的共有代码块,然后将每个入口点独有的代码块单独形成一个文件。如果你使用了第三方库,就像上面我们使用的loadsh,它会将第三方入口代码块单独打包为一个文件。配置文件webpack.config.js需要增加如下的代码:···optimization: { splitChunks: { chunks: ‘all’, maxInitialRequests: 20, maxAsyncRequests: 20, minSize: 40 }}···因为SplitChunksPlugin可以提取任意的入口点之间的共同代码,所以,我们就不需要使用vendors入口节点了。那么,为匹配生成不同的页面代码可以修改成如下:const HtmlWebapckPlugin = require(‘html-webpack-plugin’)··· plugins: [ new HtmlWebapckPlugin({ filename: ‘index.html’, chunks: [‘index’] }), new HtmlWebapckPlugin({ filename: ‘aboutUs.html’, chunks: [‘aboutUs’] }), new HtmlWebapckPlugin({ filename: ‘contactUs.html’, chunks: [‘contactUs’] }), ]···可以发现结果越来越接近我们所想。但是这里还是存在一个问题,第三方库loadsh因为在入口点index, aboutUs中被分别引入,但是构建的结果却输出了两个第三方库文件,这不是我们想要的。这个问题怎么解决呢,因为html-webpack-plugin插件的chunks选项,支持多入口节点,所以,我们可以再单独创建一个第三方库的入口节点vendors。配置代码修改如下:… entry: { index: [’./src/index.js’, ‘./src/utils/load.js’], aboutUs: [’./src/aboutUs.js’], contactUs: [’./src/contactUs.js’,’./src/utils/load.js’], vendors: [’loadsh’] }, … plugins: [ new HtmlWebapckPlugin({ filename: ‘index.html’, chunks: [‘index’, ‘vendors’] }), new HtmlWebapckPlugin({ filename: ‘aboutUs.html’, chunks: [‘aboutUs’, ‘vendors’] }), new HtmlWebapckPlugin({ filename: ‘contactUs.html’, chunks: [‘contactUs’] }), ],…注意:如果不同的入口点儿之间有依赖关系,如上面的index和vendors之间,因为index依赖于vendors,所以vendors要置于index之前。这篇文章,说到这里基本上已经结束了。当然,webpack多页面应用的知识点还没有讲完,这些内容会放在后续的文章中详解。源代码webpack3.x multi-pagewebpack4.x multi-page ...

December 17, 2018 · 2 min · jiezi