Webpack4学习笔记(六)——Webpack4+VueJS2项目搭建
这是一个关于Webpack4的文章系列,其中包含以下文章:
- Webpack4学习笔记(一)——基本使用
- Webpack4学习笔记(二)——代码分割(单入口)
- Webpack4学习笔记(三)——代码分割(多入口)
- Webpack4学习笔记(四)——CSS处理
- Webpack4学习笔记(五)——分离production和development环境
- Webpack4学习笔记(六)——Webpack4+VueJS2项目搭建
本系列文章源代码:
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
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
上一篇: MySQL学习:创建数据库表
推荐阅读
-
MVC项目结构搭建及单个类的实现学习笔记1
-
Webpack4 学习笔记六 多页面配置和devtool
-
Angular4学习笔记之准备和环境搭建项目
-
Hadoop源码学习笔记之NameNode启动流程分析一:源码环境搭建和项目模块及NameNode结构简单介绍
-
分布式基础&项目环境搭建_学习笔记
-
Webpack4学习笔记(六)——Webpack4+VueJS2项目搭建
-
[原]XMPP协议学习笔记六(搭建Tigase开发环境)_MySQL
-
Webpack4 学习笔记六 多页面配置和devtool
-
[原]XMPP协议学习笔记六(搭建Tigase开发环境)_MySQL
-
Hadoop源码学习笔记之NameNode启动流程分析一:源码环境搭建和项目模块及NameNode结构简单介绍