webpack4打包vue前端多页面项目


Posted in Javascript onSeptember 17, 2018

之前一直用的脚手架,这次自己搭建webpack前端项目,花费了不少心思,于是做个总结。

1.用法

项目结构如下:

project
 |- bulid          <!-- 这个目录是自动生成的-->
    |- public
    |- css
    |- js
    |- page1.html       <!-- 插件生成的html文件-->
    |- page2.html       <!-- 插件生成的html文件-->
    ...
 |- public/         <!-- 存放字体、图片、网页模板等静态资源-->
 |- src           <!-- 源码文件夹-->
    |- components/
    |- css/
    |- js/
    |- page1.js        <!-- 每个页面唯一的VUE实例,需绑定到#app-->
    |- page2.js        <!-- 每个页面唯一的VUE实例,需绑定到#app-->
    ...
 |- package.json
 |- package-lock.json
 |- README.md

public文件夹存放一些静态文件,src文件夹存放源码。每个页面通过一个入口文件(page1.js,page2.js,..)生成vue实例,挂载到插件生成的html文件的#app元素上。

安装依赖

$ npm install

进入开发模式

$ npm run start

浏览器会打开 http://localhost:3000 ,这时页面一片空白,显示 cannot get几个字。不要慌,在url后面加上 /page1.html ,回车,便可看见我们的页面。 这是因为我把开发服务器的主页设置为 index.html ,而本例中页面为 page1.html,page2.html,因此会显示一片空白。

开发完成了,构建生产版本:

$ npm run build

这会产生一个build/文件夹,里面的文件都经过优化,服务器响应的资源,就是来自于这个文件夹。

2.介绍

2.1 webpack基础配置

我们的开发分为生产环境和开发环境,因此需要有2份webpack的配置文件(可能你会想用env环境变量,然后用3目运算符根据env的值返回不同值。然而这种方法在webpack导出模块的属性中无效,我试过~~~)。这里我们拆分成3个文件,其中 webpack.common.js 是常规的配置,在两种环境下都会用到, webpack.dev.js 和 webpack.prod.js 则是在2种环境下的特有配置。这里用到 webpack-merge 这个包,将公共配置和特有配置进行合成。

webpack.common.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const devMode = process.env.NODE_ENV !=='production';
// 需要被打包入口文件数组
// 数组元素类型 {string|object}
// string:将以默认规则生成bundle
// object{filename|title|template} 生成的bundle.html的文件名|title标签内容|路径 /public 下的模板文件(需指定文件后缀)
const entryList = [
  'page1',
  'page2',
];
/**
 * @param {array} entryList
 * @param {object} option:可选 要手动配置的内容
 */
const createEntry = (list = [], option = {}) => {
  const obj = {};
  list.forEach((item) => {
    const name = item.filename ? `./js/${item.filename}` : `./js/${item}`;
    obj[name] = path.resolve(__dirname, './src', `./${item}.js`);
  });
  return Object.assign(obj, option);
};
module.exports = {
  entry: createEntry(entryList),
  output: {
    path: path.resolve(__dirname, './build'),
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'],
          },
        },
      },
      {
        test: /\.vue$/,
        use: 'vue-loader',
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        use: {
          loader: 'file-loader',
          options: {
            name: 'public/fonts/[name].[ext]',
          },
        },
      },
      {
        test: /\.(png|svg|jpg|gif)$/,
        use: {
          loader: 'file-loader',
          options: {
            name: 'public/images/[name].[ext]',
          },
        },
      },
    ],
  },
  plugins: createPluginInstance(entryList).concat([
    // vue SFCs单文件支持
    new VueLoaderPlugin(),
  ]),
};

这里我们没有进行css文件的配置,是因为生产环境下需要优化、提取,所以在另外2个文件分别配置。

webpack.dev.js
const webpack = require('webpack');
const path = require('path');
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
  mode: 'development',
  devtool: 'inline-source-map',
  output: {
    filename: '[name].js',
    chunkFilename: '[name].js',
  },
  module: {
    rules: [
      {
        test: /\.(css|less)$/,
        use: [
          'vue-style-loader',
          'css-loader', 
          'postcss-loader',
          'less-loader'
        ],
      },
    ],
  },
  resolve: { alias: { vue: 'vue/dist/vue.js' } },
});

vue分为开发版本和生产版本,最后一行是根据路径指定使用哪个版本。

webpack.prod.js

const webpack = require('webpack');
const merge = require('webpack-merge');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const common = require('./webpack.common.js');

module.exports = merge(common, {
  mode: 'production',
  output: {
    filename: '[name].[contenthash].js',
    chunkFilename: '[name].[contenthash].js',
  },
  resolve: { alias: { vue: 'vue/dist/vue.min.js' } },
  module: {
    rules: [
      {
        test: /\.(css|less)$/,
        use: [
          MiniCssExtractPlugin.loader, 
          'css-loader', 
          'postcss-loader',
          'less-loader'
        ],
      },
    ],
  },

在production环境下,我们使用了哈希值便于缓存,以后往生产环境下添加其他资源都会如此。

2.2 解决文件输出目录

我们期待的build文件夹具有如下结构:

build
  |- css/
  |- js/
  |- page1.html
  |- page2.html
  ...

即文件按照类型放在一起,html文件直接放在该目录下,可是我们上面的配置的输出结果是混合在一起的。由于name属性既可以是文件名,也可以是 /dir/a 之类带有路径的文件名,我们根据这个特点做出一些修改。

直接对output的输出路径更改

比如改为 build/js ,其他资源利用相对路径比如 ../page1.html 进行修改。我一开始就这样做的,但最终会导致开发服务器无法响应文件的变化,因为他只能针对输出目录下的文件进行监听,该目录之上的文件变化无能为力。

修改入口名称

这也是我们的最终解决方案。将原来的文件名 page1 修改为 /js/page1 ,最终输出的js文件便都会放在js文件夹里。在生产环境下我们通过 MiniCssExtractPlugin 这个插件提取js文件中的css,这是该插件的配置:

new MiniCssExtractPlugin({
      filename:'[name].[contenthash].css'
    })

这里的name就是当初入口的名字,受到入口名称更改的影响,上面最终会变成 js/page1.131de8553ft82.css ,并且该占位符[name]只在编译时有效,这意味着无法用函数对该值进行处理。因此不能使用[name]占位符达到想要的目的,干脆只用[id]。

new MiniCssExtractPlugin({
      filename:'/css/[id].[contenthash].css'
    })

3.代码分割

在webpack4中使用optimization.splitChunks进行分割.

//webpack.common.js
const path = require('path');
module.exports = {
  // ... 省略其他内容
  optimization:{
    runtimeChunk:{
      name:'./js/runtime'
    },
    splitChunks:{
      // 避免过度分割,设置尺寸不小于30kb
      //cacheGroups会继承这个值
      minSize:30000,
      cacheGroups:{
        //vue相关框架
        main:{
          test: /[\\/]node_modules[\\/]vue[\\/]/,
          name: './js/main',
          chunks:'all'
        },
        //除Vue之外其他框架
        vendors:{
          test:/[\\/]node_modules[\\/]?!(vue)[\\/]/,
          name: './js/vendors',
          chunks:'all'
        },
        //业务中可复用的js
        extractedJS:{
          test:/[\\/]src[\\/].+\.js$/,
          name:'./js/extractedJS',
          chunks:'all'
        }
        
      }
    }
  }
};

runtimeChunk包含了一些webapck的样板文件,使得你在不改变源文件内容的情况下打包,哈希值仍然改变,因此我们把他单独提取出来,点这儿了解更多。 cacheGroups用于提取复用的模块,test会尝试匹配( 模块的绝对路径||模块名 ),返回值为true且满足条件的模块会被分割。满足的条件可自定义,比如模块最小应该多大尺寸、至少被导入进多少个chunk(即复用的次数)等。默认在打包前模块不小于30kb才被会分割。

4.树抖动

在package.json里加入

"sideEffects":["*.css","*.less","*.sass"]

该数组之外的文件将会受到树抖动的影响——未使用的代码将会从export导出对象中剔除。这将大大减少无用代码。如果树抖动对某些文件具有副作用,就把这些文件名放进数组以跳过此操作。css文件(包括.less,.sass)都必须放进来,否则会出现样式丢失。

5. 插件的使用

5.1 clean-webpack-plugin

每次打包后都会生成新的文件,这可能会导致无用的旧文件堆积,对于这些无用文件自己一个个删太麻烦,这个插件会在每次打包前自动清理。实际中,我们不想在开发环境下清理掉build命令生成的文件,因此只在生产环境使用了这个插件。

5.2 html-Webpack-plugin

我们的源码目录中并没有html文件,打包后的多个html文件,就是我们用这个插件生成的。

//webpack.common.js
// ...省略上面已经出现过的内容
//每个html需要一个插件实例
//批量生成html文件
const createPluginInstance = (list = []) => (
  list.map((item) => {
    return new HtmlWebpackPlugin({
      filename: item.filename ? `${item.filename}.html` : `${item}.html`,
      template: item.template ? `./public/${item.template}` :       './public/template.html',
      title: item.title ? item.title : item,
      chunks: [
        `./js/${item.filename ? item.filename : item}`,
        './js/extractedJS',
        './js/vendors',
        './js/main',
        './js/runtime',
        './css/styles.css',
        devMode ? './css/[id].css' : './css/[id].[contenthash].css',
      ],
    });
  })
);

默认会将所有的入口文件,代码分割后的文件打包进一个html文件里,通过指定 chunks 属性来告诉插件 只包含 哪些块,或者exludeChunks指定不应包含那些chunks。这里有个小问题,我们无法让文件刚好只包含他需要的块。若想不包含未使用的chunks,只能根据实际情况手动配置,用这个函数批量生成的文件,总会包含所有的公共打包文件。

5.3 mini-css-extract-plugin (prooduction)

该插件用于提取js文件中的css到单独的css文件中。

//webpack.prod.js
//...省略其他内容
plugins:[
    new CleanWebpackPlugin('build'), 
    // 提取css
    new MiniCssExtractPlugin({
      filename:'./css/[id].[contenthash].css'
    }),
    //优化缓存
    new webpack.HashedModuleIdsPlugin()
  ]

5.4 optimize-css-assets-webpack-plugin (production)

用于精简打包后的css代码,设置在配置optimization的minimizer属性中,这将会覆盖webpack默认设置,因此也要同时设置js的精简工具(这里我们用uglifyplugin插件):

optimization: {
    minimizer:[
     new UglifyJsPlugin({
      cache: true,
      parallel: true
     }),
     new OptimizeCSSAssetsPlugin()
    ]
  }

6.开发服务器、热模块替换 (development)

webpack.dev.js中增加如下内容即可:

//...省略其他内容
devServer:{
    index:'index.html',
    hot:true,
    contentBase:path.resolve(__dirname,'./build'),
    port:3000,
    noInfo:true
  },
plugins:[
    new webpack.HotModuleReplacementPlugin()
  ]

使用开发服务器可以在我们修改了源文件后自动刷新,因为是将数据放在内存中,因此不会影响硬盘中build文件夹。热模块替换还需要在源文件做相应修改。我们也为动态导入语法进行了相应配置。

7.其他

public用于存放静态资源,打包后也会在build/下创建一个同名文件夹,里面存放的是public会被使用到的资源。如果在.css文件里引用了public里的资源,如图片,添加url的时候要使用绝对路径:

<!-- src/css/page1.css -->
.bg-img {
  background-image:url(/public/images/1.jpg)
}

这样通过 http/https 打开的时候就能正常使用,如果是以文件形式打开(比如打包后双击page1.html),会发现浏览器显示无法找到资源。通过导入图片作为变量引用( import name from path ),既可使用绝对路径,也可使用相对路径。

总结

以上所述是小编给大家介绍的webpack4打包vue前端多页面项目,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
JavaScript 面向对象的 私有成员和公开成员
May 13 Javascript
jquery的Tooltip插件 qtip使用详细说明
Sep 08 Javascript
JS实现可拖曳、可关闭的弹窗效果
Sep 26 Javascript
jQuery侧边栏实现代码
May 06 Javascript
不同js异步函数同步的实现方法
May 28 Javascript
D3.js实现柱状图的方法详解
Sep 21 Javascript
浅谈javascript alert和confirm的美化
Dec 15 Javascript
如何利用JQuery实现从底部回到顶部的功能
Dec 27 Javascript
基于javascript实现碰撞检测
Mar 12 Javascript
Jquery $.map使用方法实例详解
Sep 01 jQuery
基于Cesium绘制抛物弧线
Nov 18 Javascript
jQuery实现穿梭框效果
Jan 19 jQuery
node中的密码安全(加密)
Sep 17 #Javascript
Vue CLI3搭建的项目中路径相关问题的解决
Sep 17 #Javascript
浅谈webpack SplitChunksPlugin实用指南
Sep 17 #Javascript
vue的过滤器filter实例详解
Sep 17 #Javascript
一步一步的了解webpack4的splitChunk插件(小结)
Sep 17 #Javascript
React Router V4使用指南(精讲)
Sep 17 #Javascript
关于vue编译版本引入的问题的解决
Sep 17 #Javascript
You might like
PHP删除HTMl标签的实现代码
2013/06/30 PHP
php获取中文拼音首字母类和函数分享
2014/04/24 PHP
PHP实现绘制3D扇形统计图及图片缩放实例
2014/10/01 PHP
ThinkPHP5.1+Ajax实现的无刷新分页功能示例
2020/02/10 PHP
php使用fputcsv实现大数据的导出操作详解
2020/02/27 PHP
JQuery $.each遍历JavaScript数组对象实例
2014/09/01 Javascript
JavaScript动态修改弹出窗口大小的方法
2015/04/06 Javascript
JavaScript淡入淡出渐变简单实例
2015/08/06 Javascript
js+html5实现半透明遮罩层弹框效果
2020/08/24 Javascript
vue主动刷新页面及列表数据删除后的刷新实例
2018/09/16 Javascript
小程序采集录音并上传到后台
2019/11/22 Javascript
Vue项目利用axios请求接口下载excel
2020/11/17 Vue.js
[00:52]DOTA2第二届亚洲邀请赛预选赛宣传片
2017/01/13 DOTA
Python使用multiprocessing实现一个最简单的分布式作业调度系统
2016/03/14 Python
深入浅析ImageMagick命令执行漏洞
2016/10/11 Python
浅析python协程相关概念
2018/01/20 Python
Python返回数组/List长度的实例
2018/06/23 Python
Django框架自定义session处理操作示例
2019/05/27 Python
Python 使用 attrs 和 cattrs 实现面向对象编程的实践
2019/06/12 Python
如何基于Python获取图片的物理尺寸
2019/11/25 Python
Python使用pymysql模块操作mysql增删改查实例分析
2019/12/19 Python
Python控制鼠标键盘代码实例
2020/12/08 Python
CSS实现的一闪而过的图片闪光效果
2014/04/23 HTML / CSS
CSS3 transition 实现通知消息轮播条
2020/10/14 HTML / CSS
图库照片、免版税图片、矢量艺术、视频片段:Depositphotos
2019/08/02 全球购物
大四自我鉴定
2014/02/08 职场文书
中国梦主题教育活动总结
2014/05/05 职场文书
四风剖析查摆对照检查材料思想汇报
2014/09/24 职场文书
个人向公司借款协议书
2014/10/09 职场文书
优秀班主任申报材料
2014/12/16 职场文书
全国助残日活动总结
2015/05/11 职场文书
消防安全主题班会
2015/08/12 职场文书
小学二年级语文教学反思
2016/03/03 职场文书
话题作文之财富(600字)
2019/12/03 职场文书
深入理解python协程
2021/06/15 Python
详解Mysql事务并发(脏读、不可重复读、幻读)
2022/04/29 MySQL