欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Webpack4学习笔记(六)——Webpack4+VueJS2项目搭建

程序员文章站 2022-05-30 17:44:42
...

这是一个关于Webpack4的文章系列,其中包含以下文章:

本系列文章源代码:

git clone https://github.com/davenkin/webpack-learning.git

上一篇文章中,我们讲到了开发环境和生产环境的分离配置,本文我们将讲到如何使用Webpack4来大家VueJS项目。

欢迎访问本文github代码

在Vue项目中,我们通常使用vue-cli来初始化项目,但是vue-cli 2所创建出来的项目中,webpack的配置比较冗繁,并且webpack 的版本还是webpack 3。而在最新的vue-cli 3中,虽然采用了webpack 4,但是vue-cli 3索性将所有的webpack配置全部内置化了,我们并不那么容易知道其内部具体发生了什么。本文试图填补vue-cli 2和vue-cli 3之间的空白,即使用Webpack4来手动搭建VueJS项目。

工程目录结构

对于VueJS工程的目录结构,网上已经有很多最佳实践,比如这里。本文也将借鉴该文章中的目录结构,创建src目录,该目录工程主要的源代码目录,其中包含以下子目录:

.
├── Dockerfile(用于构建docker镜像,基于Nginx)
├── README.md
├── nginx.conf(Nginx配置文件)
├── package.json
├── src(源代码文件夹)
│   ├── App.vue(主组件)
│   ├── api(文件夹,存放访问后端API的js文件)
│   ├── app.html(入口HTML文件模板)
│   ├── app.js(入口js文件)
│   ├── assets(文件夹,用于存放图片、字体等资源文件,需要webpack处理)
│   │   ├── big-image.jpg
│   │   └── small-image.jpg
│   ├── components(文件夹,用于存放公用组件)
│   │   └── HelloWorld.vue
│   ├── pages(文件夹,用于存放页面级别的组件,每个page都有对应路由)
│   │   ├── Page1.vue
│   │   └── Page2.vue
│   ├── router(文件夹,用于存放路由配置)
│   │   └── index.js
│   ├── store(文件夹,用于存放vuex相关文件)
│   ├── styles(文件夹,用于存放css、sass文件)
│   │   ├── _global.scss
│   │   ├── _reset.scss
│   │   └── _variables.scss
│   └── utils(文件夹,用于存放工具文件)
├── static(文件夹,用于存放图片等资源文件,与assets不同的是,static文件夹下是文件不会经过webpack处理,而是直接被拷贝到输出目录中)
│   └── normal-image.jpg
├── webpack.base.conf.js(公共webpack配置文件)
├── webpack.dev.conf.js(development环境配置文件)
└── webpack.prod.conf.js(production环境配置文件)

入口HTML文件——src/app.html

入口HTML文件app.html只是一个模板文件,构建时webpack会将输出的js和css文件自动注入到该文件中,生成最终的index.html文件。app.html文件中应该有一个id="app"的div用于对应app.js文件中的el: '#app'

app.html文件内容如下:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1.0">
  <title>Webpack4+VueJS2</title>
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

入口js文件——src/app.js

该文件是webpack的入口文件,用于创建Vue实例,并启动整个Vue框架:

import Vue from "vue";
import App from "./App.vue";
import router from "./router";

Vue.config.productionTip = false;

new Vue({
  el: '#app',
  router,
  components: {App},
  template: '<App/>'
});

可以看到,在app.js中import了Vue官方库(在webpack.base.conf.js中,配置了名为vue的别名'vue$': 'vue/dist/vue.esm.js'),另外还import了项目主组件App.vue和路由配置。对于路由配置import router from "./router";./router只是一个文件夹,因此此时webpack会加载该文件夹下的index.js文件。另外,由于我们在webpack.base.conf.js中配置了extensions: ['.js', '.vue', '.json'],表示不止是js文件,vue和json文件均可以采用相同的方式进行import。

入口vue文件——src/App.vue

该文件被app.js引用,是vue项目的入口组件。在该文件中,我们创建了两个路由链接,分别链接到Page1.vue和Page2.vue,并通过<router-view/>加载Page1和Page2。

<template>
  <div id="app">
    <div>
      <router-link to="/page1">Go to Page 1</router-link>
      <router-link to="/page2">Go to Page 2</router-link>
    </div>
    <router-view/>
  </div>
</template>

<script>
  export default {
    name: 'App'
  }
</script>

<style lang="scss">
  @import './styles/_reset.scss';
  @import './styles/_global.scss';
</style>

此外,我们还可以看到,我们采用了sass,并且在App.vue中引入了两个全局的sass文件,一个是meyer的_reset.scss,一个是自定义的全局_global.scss。

路由配置——src/router/index.js

前面提到,在App.vue文件中有两个链接分别链接到Page1和Page2,路由配置如下:

import Vue from "vue";
import Router from "vue-router";
import Page1 from "@/pages/Page1";
import Page2 from "@/pages/Page2";

Vue.use(Router);

export default new Router({
  routes: [
    {
      path: '/page1',
      name: 'Page1',
      component: Page1
    },
    {
      path: '/page2',
      name: 'Page2',
      component: Page2
    }
  ]
})

页面组件和公用组件

在Vue中,页面组件表示对应有路由的页面,这些组件被单独地放到了pages目录中,而公用组件表示与某个页面或者路由无关的组件,任何地方都可以引入这些组件,比如对话框组件,日历组件等。

在本例中,我们有Page1和Page2两个页面,因此对应有Page1.vue和Page2.vue两个组件。另外,两个组件都引入了公用组件HelloWorld.vue组件。

Page1组件:

<template>
  <div :class="$style.yellow">
    <hello-world/>
  </div>
</template>

<script>
  import HelloWorld from '@/components/HelloWorld';
  export default {
    components: {
      HelloWorld
    }
  }
</script>

<style module lang="scss">
  .yellow {
    background-color: yellow;
  }
</style>

HelloWorld.vue组件:

<!--公用组件-->
<template>
  <div :class="$style.hello">Hello World 1! {{msg}}</div>
</template>

<script>
  export default {
    data () {
      return {
        msg: [1, 2, 4].map((n) => n + 1)
    }
    }
  }
</script>

<style module lang="scss">
  .hello {
    background-color: red;
    width: 300px;
    height: 300px;
  }
</style>

可以看到,我们采用了css module(<style module lang="scss">),此时我们可以在vue组件中通过$style变量访问到该组件的css,比如:class="$style.hello"。另外,在Page1.vue中引入HelloWorld.vue时,我们采用了import HelloWorld from '@/components/HelloWorld';,其中的@是一个被赋予了特殊含义的字符,即在webpack.base.conf.js中,我们通过'@': path.resolve(__dirname, 'src')定义了一个名为@的别名,该别名指向了src文件夹。

css处理

对于sass而言,我们通常都会通过变量定义一些统一风格的数值,比如颜色、margin值等。此时我们可以创建一个_variable.scss文件,该文件中只定义变量,然后在其他sass文件或者Vue文件的<style>块中@import该变量文件。但是,如此一来我们可能需要在多个地方引入该_variable.scss文件文件。如果能在一个地方引入然后大家都能使用就好了,答案是有的:在webpack的sass-loader的配置中可以加入data配置项:data: '@import "./src/styles/_variables.scss";',然后在其他css文件中,无需再次引入_variables.scss文件便可直接引用该文件中的变量。

这里我们还一并加入了postcss-loader。最终对于css/scss文件的处理需要使用到4个loader,从前到后分别为sass-loader、postcss-loader、css-loader和vue-style-loader(development)/MiniCssExtractPlugin.loader(production),以production环境为例:

...
{
        test: /\.(s*)css$/,
        use: [
          MiniCssExtractPlugin.loader,
          {
            loader: 'css-loader',
            options: {
              sourceMap: true,
              modules: true,
              localIdentName: '[name]---[local]---[hash:base64:5]'
            }
          },
          {
            loader: 'postcss-loader',
            options: {
              plugins: [require("autoprefixer")],
              sourceMap: true
            }
          },
          {
            loader: 'sass-loader',
            options: {
              sourceMap: true,
              data: '@import "./src/styles/_variables.scss";'
            }
          }
        ]
      }
...

请注意,test: /\.(s*)css$/,需要同时处理css和scss文件。

webpack配置文件

webpack.base.conf.js文件:

const path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const webpack = require('webpack');

module.exports = {
  entry: {
    'app': './src/app.js'
  },
  output: {
    filename: '[name].[contenthash].js',
    chunkFilename: '[name].[contenthash].js',
    path: path.resolve(__dirname, 'distribution')
  },
  resolve: {
    extensions: ['.js', '.vue', '.json'],
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
      '@': path.resolve(__dirname, 'src')
    }
  },

  module: {
    rules: [
      {
        test: /\.js$/,
        loader: 'babel-loader',
        include: [path.resolve(__dirname, 'src')],
        exclude: [path.resolve(__dirname, 'node_modules')]
      },
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: '[name].[hash:7].[ext]'
        }
      },
      {
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: '[name].[hash:7].[ext]'
        }
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: '[name].[hash:7].[ext]'
        }
      }
    ]
  },
  plugins: [
    new CleanWebpackPlugin(['distribution']),
    new HtmlWebpackPlugin({
      template: './src/app.html',
      filename: 'index.html'
    }),
    new VueLoaderPlugin(),
    new CopyWebpackPlugin([
      {
        from: path.resolve(__dirname, 'static'),
        to: path.resolve(__dirname, 'distribution')
      }
    ])
  ]

};

webpack.dev.conf.js文件:

const merge = require('webpack-merge');
const webpack = require('webpack');
const baseWebpackConfig = require('./webpack.base.conf');

const webpackConfig = merge(baseWebpackConfig, {
  //environment specific config goes here
  mode: 'development',
  output: {
    filename: '[name].js',
    chunkFilename: '[name].js'
  },
  devtool: 'inline-source-map',
  devServer: {
    contentBase: './distribution',
    inline: true,//do not use iframe for the page, true is default
    open: true,//open browser after dev server starts
    port: 8080,//8080 is default
    proxy: {//proxy backend api
      '/api': 'http://localhost:3000'
    },
    hot: true
  },
  module: {
    rules: [
      {
        test: /\.(s*)css$/,
        use: [
          'vue-style-loader',
          {
            loader: 'css-loader',
            options: {
              sourceMap: true,
              modules: true,
              localIdentName: '[name]---[local]---[hash:base64:5]'
            }
          },
          {
            loader: 'postcss-loader',
            options: {
              plugins: [require("autoprefixer")],
              sourceMap: true
            }
          },
          {
            loader: 'sass-loader',
            options: {
              sourceMap: true,
              data: '@import "./src/styles/_variables.scss";'
            }
          }
        ]
      }
    ]
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin()
  ]

});

module.exports = webpackConfig;

webpack.prod.conf.js文件:

const merge = require('webpack-merge');
const webpack = require('webpack');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const baseWebpackConfig = require('./webpack.base.conf');

const webpackConfig = merge(baseWebpackConfig, {
  //environment specific config goes here
  mode: 'production',
  devtool: 'source-map',
  module: {
    rules: [
      {
        test: /\.(s*)css$/,
        use: [
          MiniCssExtractPlugin.loader,
          {
            loader: 'css-loader',
            options: {
              sourceMap: true,
              modules: true,
              localIdentName: '[name]---[local]---[hash:base64:5]'
            }
          },
          {
            loader: 'postcss-loader',
            options: {
              plugins: [require("autoprefixer")],
              sourceMap: true
            }
          },
          {
            loader: 'sass-loader',
            options: {
              sourceMap: true,
              data: '@import "./src/styles/_variables.scss";'
            }
          }
        ]
      }
    ]
  },
  optimization: {
    runtimeChunk: {
      "name": "manifest"
    },
    splitChunks: {
      cacheGroups: {
        default: false,
        vendors: false,
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all'
        }

      }
    }
  },
  plugins: [
    new webpack.HashedModuleIdsPlugin(),
    new MiniCssExtractPlugin({
      filename: "[name].[contenthash].css",
      chunkFilename: "[name].[contenthash].css"
    })
  ]
});

module.exports = webpackConfig;

 



作者:无知者云
链接:https://www.jianshu.com/p/df1b82dfd483
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。