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 document.referrer判断访客来源网址
May 15 Javascript
js继承的实现代码
Aug 05 Javascript
分享精心挑选的23款美轮美奂的jQuery 图片特效插件
Aug 14 Javascript
jQuery的bind()方法使用详解
Jul 15 Javascript
JavaScript数组对象赋值用法实例
Aug 04 Javascript
Bootstrap模块dropdown实现下拉框响应
May 22 Javascript
Js查找字符串中出现次数最多的字符及个数实例解析
Sep 05 Javascript
详谈javascript精度问题与调整
Jul 08 Javascript
js异步编程小技巧详解
Aug 14 Javascript
运用js实现图层拖拽的功能
May 24 Javascript
Vue 实现复制功能,不需要任何结构内容直接复制方式
Nov 09 Javascript
详解vue3.0 diff算法的使用(超详细)
Jul 01 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
php5 apache 2.2 webservice 创建与配置(java)
2011/01/27 PHP
windows下PHP_intl.dll正确配置方法(apache2.2+php5.3.5)
2014/01/14 PHP
Aster vs KG BO3 第二场2.18
2021/03/10 DOTA
基于jquery插件实现常见的幻灯片效果
2013/11/01 Javascript
js给网页加上背景音乐及选择音效的方法
2015/03/03 Javascript
浅谈document.write()输出样式
2015/05/07 Javascript
JQuery球队选择实例
2015/05/18 Javascript
探寻JavaScript中this指针指向
2016/04/23 Javascript
jQuery防止重复绑定事件的解决方法
2016/05/14 Javascript
JavaScript基础之AJAX简单的小demo
2017/01/29 Javascript
js实现一键复制功能
2017/03/16 Javascript
JS实现数组按升序及降序排列的方法
2017/04/26 Javascript
node.js+express+mySQL+ejs+bootstrop实现网站登录注册功能
2018/01/12 Javascript
详解webpack-dev-server使用方法
2018/09/14 Javascript
使用Angular自定义字段校验指令的方法示例
2019/02/01 Javascript
js实现数字滚动特效
2019/12/16 Javascript
微信小程序停止其他视频播放当前视频的实例代码
2019/12/25 Javascript
[01:04:06]DOTA2上海特级锦标赛A组资格赛#2 Secret VS EHOME第一局
2016/02/26 DOTA
python通过ftplib登录到ftp服务器的方法
2015/05/08 Python
Python的爬虫包Beautiful Soup中用正则表达式来搜索
2016/01/20 Python
Django自定义分页与bootstrap分页结合
2021/02/22 Python
python try except返回异常的信息字符串代码实例
2019/08/15 Python
python+tkinter实现学生管理系统
2019/08/20 Python
python实现把二维列表变为一维列表的方法分析
2019/10/08 Python
python tqdm库的使用
2020/11/30 Python
Html5大文件断点续传实现方法
2015/12/05 HTML / CSS
英国在线定制百叶窗网站:Swift Direct Blinds
2020/02/25 全球购物
OSPF有什么优点?为什么OSPF比RIP收敛快?
2013/02/13 面试题
俄语专业毕业生推荐信
2013/10/28 职场文书
航空大学应届生求职信
2013/11/10 职场文书
高中生职业生涯规划书
2014/02/24 职场文书
企业员工培训感言
2014/02/26 职场文书
俞敏洪北大演讲稿
2014/05/22 职场文书
学生党员检讨书范文
2014/12/27 职场文书
民主生活会主持词
2015/07/01 职场文书
2019个人年度目标制定攻略!
2019/07/12 职场文书