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 兼容鼠标滚轮事件
Apr 07 Javascript
Javascript 判断函数类型完美解决方案
Sep 02 Javascript
JQuery中根据属性或属性值获得元素(6种情况获取方法)
Jan 17 Javascript
原生JS可拖动弹窗效果实例代码
Nov 09 Javascript
Document.body.scrollTop的值总为零的快速解决办法
Jun 09 Javascript
浅谈angular4实际项目搭建总结
Dec 01 Javascript
VUE2.0中Jsonp的使用方法
May 22 Javascript
vue 实现左右拖拽元素并且不超过他的父元素的宽度
Nov 30 Javascript
JSX在render函数中的应用详解
Sep 04 Javascript
vue v-for直接循环数字实例
Nov 07 Javascript
微信小程序实现时间进度条功能
Nov 17 Javascript
javascript遍历对象的五种方式实例代码
Oct 24 Javascript
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实现的下载css文件中的图片的代码
2010/02/08 PHP
解析php中call_user_func_array的作用
2013/06/07 PHP
php和jquery实现地图区域数据统计展示数据示例
2014/02/12 PHP
zf框架的校验器使用使用示例(自定义校验器和校验器链)
2014/03/13 PHP
php使用ob_start()实现图片存入变量的方法
2014/11/14 PHP
php遍历CSV类实例
2015/04/14 PHP
PHP单例模式详细介绍
2015/07/01 PHP
js中将多个语句写成一个语句的两种方法小结
2007/12/08 Javascript
js自动下载文件到本地的实现代码
2013/04/28 Javascript
JavaScript使用function定义对象并调用的方法
2015/03/23 Javascript
jQuery弹簧插件编写基础之“又见弹窗”
2015/12/11 Javascript
js实现textarea限制输入字数
2017/02/13 Javascript
r.js来合并压缩css文件的示例
2018/04/26 Javascript
Node.js之删除文件夹(含递归删除)代码实例
2019/09/09 Javascript
非常漂亮的js烟花效果
2020/03/10 Javascript
[05:13]TI4 中国战队 机场出征!!
2014/07/07 DOTA
使用Python对MySQL数据操作
2017/04/06 Python
解决Linux系统中python matplotlib画图的中文显示问题
2017/06/15 Python
Numpy数组转置的两种实现方法
2018/04/17 Python
Python实现的查询mysql数据库并通过邮件发送信息功能
2018/05/17 Python
对Python 内建函数和保留字详解
2018/10/15 Python
人工神经网络算法知识点总结
2019/06/11 Python
python字符串中匹配数字的正则表达式
2019/07/03 Python
Android Q之气泡弹窗的实现示例
2020/06/23 Python
python opencv pytesseract 验证码识别的实现
2020/08/28 Python
iphoneX 适配客户端H5页面的方法教程
2017/12/08 HTML / CSS
俄罗斯街头服装品牌:Black Star Wear
2017/03/01 全球购物
大学生旷课检讨书
2014/01/22 职场文书
新年爱情寄语
2014/04/08 职场文书
司法局群众路线教育实践活动开展情况总结
2014/10/25 职场文书
2014年个人业务工作总结
2014/11/17 职场文书
Nginx中break与last的区别详析
2021/03/31 Servers
php中pcntl_fork详解
2021/04/01 PHP
Pytorch 统计模型参数量的操作 param.numel()
2021/05/13 Python
Java使用Unsafe类的示例详解
2021/09/25 Java/Android
Go语言特点及基本数据类型使用详解
2022/03/21 Golang