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

这是一个关于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;


Original url: Access
Created at: 2019-05-05 16:59:24
Category: default
Tags: none

请先后发表评论
  • 最新评论
  • 总共0条评论