# 手动搭建Vue项目

TIP

💡 为了能更好的学习和理解Vue以及webpack的配置,所以试一下不用vue-cli,手动搭建一下Vue项目。🙋‍♀️

# 一、初始化项目

👉 新建一个项目文件夹,进入文件夹,运行 npm init -y 初始化项目(-y可以直接跳过输入,直接初始化),初始化成功之后项目会生成package.json文件

👉 安装 vue和webpack-cli(因为webpack4之后强制要求安装webpack-cli), 然后执行 npm i webpack-cli vue -D

👉 安装项目所需要的标配依赖包,运行 npm i vue-loader css-loader style-loader vue-template-compiler -D

  • vue-loader: 处理.vue文件
  • css-loader: 加载.css文件,处理css中url的路径,解析@import等语句
  • style-loader: 用style标签将css-loader解析出来的内部样式注入到我们的HTML页面
  • vue-template-compiler: 处理template模板的依赖包

👉 在根目录新建src文件夹(存放项目源代码),在src文件夹创建index.js文件和app.vue文件;在根目录下创建build文件夹(存放webpack配置),并在文件夹下新建webpack.config.js文件
项目结构如下:

TIP

|——build  
|   |__webpack.config.js  
|——node_module  
|——src  
|   |——app.vue  
|   |__index.js      
|——package-lock.json  
|——package.json  

👉 在index.js和app.vue加入以下代码

  • index.js
// index.js入口文件
import Vue from 'vue'
import App from './app.vue'

// 创建一个Vue实例App, mount就是讲我们的App挂载到root这样一个根节点中去
new Vue({
    render: (h) =>h(App)
}).$mount('#app')
  • app.vue
<template>
    <div>
        <h1 class="txt">{{text}}</h1>
        <div class="play"></div>
        <router-view/>
    </div>
</template>
<script>
export default {
    data(){
        return{
            text:'Hello banana 🍌'
        }
    }
}
</script>
<style>
.txt{color:brown;text-align:center;}
</style>

👉 打开webpack.config.js开始进行webpack的基本配置

//webpack.config.js 基本配置
const path = require('path');//nodejs里面的基本包,用来处理路径
const { VueLoaderPlugin } = require('vue-loader');//或者const VueLoaderPlugin = require('vue-loader/lib/plugin');

module.exports = {
    // 入口文件
    entry: path.join(__dirname,'../src/index.js'),
    // 出口文件
    output:{
        filename: 'boundle.js',
        path: path.join(__dirname,'../dist')
    },
    mode:'none',//none development production
    plugins:[
        new VueLoaderPlugin()// make sure to include the plugin for the magic
    ],
    module:{
        rules:[
            {
                test: /\.vue$/,
                loader: 'vue-loader'//通过vue-loader来识别以vue结尾的文件
            },
            {
                test: /\.css$/,//css的处理方式不同,有嵌入在页面style标签里的,有从外部文件引入的,我们这里用use来声明
                use:[
                    'style-loader',//注意:style-loader要在css-loader前面
                    'css-loader'
                ]
            }
        ]
    }
}

👉 在package.json添加打包命令 "biu"

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "biu": "webpack --config build/webpack.config.js"
  },

这个时候就可以运行npm run biu了, biu一下会发现出来了一个dist文件夹,如果boundle.js生成成功就说明打包成功啦🎉

# 二、实现打包图片和sass、less文件

👉 接下来试着打包一下图片和scss文件(同less文件)。先在src下新建images和styles文件夹。然后加入图片和样式文件,添加成功后的项目结构就像这样:

TIP

|——build  
|   |__webpack.config.js  
|——node_module  
|——src  
|   |——images
|   |   |__play.jpg
|   |——styles
|   |   |__global.scss
|   |——app.vue  
|   |__index.js      
|——package-lock.json  
|——package.json  

在index.js添加代码 import './assets/styles/global.scss' 引进scss文件

在webpack.config.js添加处理图片和scss(less)文件的loader

    {
        test: /\.scss$/,// 处理顺序是从sass-loader到style-loader
        use:[
            'style-loader',
            'css-loader',
            'sass-loader'
        ]
    },
    {
        test: /\.scss$/,// 处理顺序是从sass-loader到style-loader
        use:[
            'style-loader',
            'css-loader',
            'less-loader'
        ]
    },
    {
        test:/\.(gif|jpg|jpeg|svg)/i,
        use:[{
            loader:'url-loader',
            options:{
                limit:8192,// 当文件大小小于limit byte时会把图片转换为base64编码的dataurl,否则返回普通的图
                name:'../dist/assets/images/[name]-[hash:5].[ext]'// 图片文件名称加上内容哈希
            }
        }]
    }

同时不要忘记要install一下npm i sass-loader node-sass file-loader(less less-loader),然后biu一下~

  • node-sass: sass-loader是对node-sass进行了一个封装,依赖于node-sass,所以要一起安装;
  • file-loader: url-loader可以设置limit,当图片大小小于上限值,url-loader可以将图片转base64字符串,能更快的加载图片;一旦图片过大,就需要使用file-loader加载图片,都是为了提高浏览器加载图片速度。同样,url-loader也依赖于file-loader。

biu~ 成功之后就能在dist下面看到打包出来的图片啦

# 三、打包 html 文件

👉 添加 html-webpack-plugin,自动生成 index.html 的内容
修改 webpack.config.js

const { VueLoaderPlugin } = require('vue-loader');//或者const VueLoaderPlugin = require('vue-loader/lib/plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    // 入口文件
    entry: path.join(__dirname,'../src/index.js'),
    // 出口文件
    output:{
        filename: 'boundle.js',
        path: path.join(__dirname,'../dist')
    },
    mode:'none',//none development production
    plugins:[
        new VueLoaderPlugin(),// make sure to include the plugin for the magic
        new HtmlWebpackPlugin({
            filename: 'index.html', // 生成的文件名称
            template: 'index.html', // 指定用index.html做模版
            inject: 'body' // 指定插入的<script>标签在body底部
        })
    ],

# 四、配置本地服务器

👉 添加 webpack-dev-server,配置更友好的开发环境,在本地启动一个服务器
修改 package.json 里的 scripts,运行 npm i webpack-dev-server -D

"biu": " webpack --config build/webpack.config.js",
"dev": "webpack-dev-server --config build/webpack.config.js"

修改 webpack.config.js,添加devServer配置

devServer : {
        // contentBase: path.join(__dirname, 'dist'),
        port: 8000,// 端口
        host: '0.0.0.0', // 配置成0.0.0.0的话通过ip,localhost都能访问
        overlay: {
            errors: true // 如果有编译错误的话直接显示到页面上
        }
    }

然后打开 http://0.0.0.0:8000/ 就能看到dist文件夹下面的index.html啦

# 五、配置router

👉 因为vue是单页面项目,页面之间都是通过路由管理器进行部分页面的显示与刷新(是这样说吗???)运行npm i vue-router -D,在src文件夹下新建router.js(配置路由页面),新建components文件夹(放组件),随便写一个组件,helloworld吧,hellowrold.vue吧,接下来进行router.js的配置

// router.js 配置项目路由
import Vue from 'vue'
import Router from 'vue-router'
import Hello from './components/helloworld.vue'

Vue.use(Router);

export default new Router({
    routes:[
        {
            path: '/',//路径
            name: 'Hello',
            component: Hello
        }
    ]
})

在app.vue添加<router-view/>

<template>
    <div>
        <h1 class="txt">{{text}}</h1>
        <div class="play"></div>
        <router-view/>
    </div>
</template>

然后 npm run dev 一下可以在index页面看到helloworld的内容,就说明组件helloworld就挂上去啦

# 六、添加 clean-webpack-plugin,每次 build 之前可以自动先清除输出文件夹

npm i clean-webpack-plugin --save-dev ,在webpack.config.js添加如下代码

    //webpack.config.js 基本配置
    const path = require('path');//nodejs里面的基本包,用来处理路径
    const { VueLoaderPlugin } = require('vue-loader');//或者const VueLoaderPlugin = require('vue-loader/lib/plugin');
    const HtmlWebpackPlugin = require('html-webpack-plugin');
  + const { CleanWebpackPlugin } = require("clean-webpack-plugin");


    module.exports = {
        // 入口文件
        entry: path.join(__dirname,'../src/index.js'),
        // 出口文件
        output:{
            filename: 'boundle.js',
            path: path.join(__dirname,'../dist')
        },
        mode:'none',//none development production
        plugins:[
            new VueLoaderPlugin(),// make sure to include the plugin for the magic
            new HtmlWebpackPlugin({
                filename: 'index.html', // 生成的文件名称
                template: 'index.html', // 指定用index.html做模版
                inject: 'body' // 指定插入的<script>标签在body底部
            }),
      +     new CleanWebpackPlugin()
        ],
        module:{
            rules:[
                {
                    test: /\.vue$/,
                    loader: 'vue-loader'//通过vue-loader来识别以vue结尾的文件
                },
                {
                    test: /\.css$/,//css的处理方式不同,有嵌入在页面style标签里的,有从外部文件引入的,我们这里用use来声明
                    use:[
                        'style-loader',//注意:style-loader要在css-loader前面
                        'css-loader'
                    ]
                },
                {
                    test: /\.scss$/,// 处理顺序是从sass-loader到style-loader
                    use:[
                        'style-loader',
                        'css-loader',
                        'sass-loader'
                    ]
                },
                {
                    test: /\.less$/,// 处理顺序是从sass-loader到style-loader
                    use:[
                        'style-loader',
                        'css-loader',
                        'less-loader'
                    ]
                },
                {
                    test:/\.(gif|jpg|jpeg|svg)/i,
                    use:[{
                        loader:'url-loader',
                        options:{
                            limit:8192,// 当文件大小小于limit byte时会把图片转换为base64编码的dataurl,否则返回普通的图
                            name:'../dist/assets/images/[name]-[hash:5].[ext]'// 图片文件名称加上内容哈希
                        }
                    }]
                },
                {
                    test: /\.js$/,
                    exclude: /(node_modules|bower_components)/, // 不处理这两个文件夹里的内容
                    loader: 'babel-loader'//转译 es6 代码为 es5 代码
                }
            ]
        },
        devServer : {
            // contentBase: path.join(__dirname, 'dist'),
            port: 8000,
            host: '0.0.0.0', // 配置成0.0.0.0的话通过ip,localhost都能访问
            overlay: {
                errors: true // 如果有编译错误的话直接显示到页面上
            }
        }
    }

这个时候发现dev时也把dist文件夹给删掉了,这不是需要的结果,解决办法就是分离开发环境和生产环境。安装一个可以跨平台设置环境和使用环境变量的cross-env,执行npm i cross-env -D,在build文件夹新增webpack.dev.conf.js(开发环境配置)、webpack.prod.conf.js(生产环境配置)两个文件,分离之后三个文件的代码如下:

  • webpack.base.conf.js
//webpack.config.js 基本配置
const path = require('path');//nodejs里面的基本包,用来处理路径
const { VueLoaderPlugin } = require('vue-loader');//或者const VueLoaderPlugin = require('vue-loader/lib/plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

const NODE_ENV=process.env.NODE_ENV;

module.exports = {
    // 入口文件
    entry: path.join(__dirname,'../src/index.js'),
    // 出口文件
    output:{
        filename: 'boundle.js',
        path: path.join(__dirname,'../dist')
    },
    plugins:[
        new VueLoaderPlugin(),// make sure to include the plugin for the magic
        new HtmlWebpackPlugin({
            filename: 'index.html', // 生成的文件名称
            template: 'index.html', // 指定用index.html做模版
            inject: 'body' // 指定插入的<script>标签在body底部
        })
    ],
    module:{
        rules:[
            {
                test: /\.vue$/,
                loader: 'vue-loader'//通过vue-loader来识别以vue结尾的文件
            },
            {
                test: /\.css$/,//css的处理方式不同,有嵌入在页面style标签里的,有从外部文件引入的,我们这里用use来声明
                use:[
                    'style-loader',//注意:style-loader要在css-loader前面
                    'css-loader'
                ]
            },
            {
                test: /\.scss$/,// 处理顺序是从sass-loader到style-loader
                use:[
                    'style-loader',
                    'css-loader',
                    'sass-loader'
                ]
            },
            {
                test: /\.less$/,// 处理顺序是从sass-loader到style-loader
                use:[
                    'style-loader',
                    'css-loader',
                    'less-loader'
                ]
            },
            {
                test:/\.(gif|jpg|jpeg|svg)/i,
                use:[{
                    loader:'url-loader',
                    options:{
                        limit:8192,// 当文件大小小于limit byte时会把图片转换为base64编码的dataurl,否则返回普通的图
                        name:'../dist/assets/images/[name]-[hash:5].[ext]'// 图片文件名称加上内容哈希
                    }
                }]
            }
        ]
    }
}

  • webpack.dev.conf.js
//引入webpack-merge插件进行合并
const merge = require('webpack-merge');
//引入webpack.base.conf.js文件
const base = require('./webpack.base.conf');
//引入webpack
const webpack = require('webpack');
//进行合并,将webpack.base.conf.js中的配置合并到这
module.exports = merge(base, {
   //模块参数
    mode: 'development',
    devServer: {
        port: '8000',//端口号
        inline: true,
        historyApiFallback: true,//在开发单页应用时非常有用,它依赖于HTML5 history API,如果设置为true,所有的跳转将指向index.html
        hot: true//允许热加载
    },
    plugins: [
       //定义全局变量
        new webpack.DefinePlugin({
         //这里必须要解析成字符串进行判断,不然将会被识别为一个变量
            DEV: JSON.stringify('dev')
        })
    ]
});
  • webpack.prod.conf.js
const merge = require('webpack-merge');
const base = require('./webpack.base.conf');
const webpack = require('webpack');

const  { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = merge(base, {
    mode: 'production',
    plugins:[
     //使用插件定义全局变量DEV
        new webpack.DefinePlugin({
            DEV:JSON.stringify('production')
        }),
       new CleanWebpackPlugin(),
    ]

});

然后记得修改package.json的script标签

"biu": "cross-env NODE_ENV=production webpack --config build/webpack.prod.conf.js",
"dev": "cross-env NODE_ENV=development webpack-dev-server --config build/webpack.dev.conf.js"

# 七、配置bable-loader

  • webpack.config.js 添加module规则
{
    test: /\.js$/,
    exclude: /(node_modules|bower_components)/, // 不处理这两个文件夹里的内容
    loader: 'babel-loader'//转译 es6 代码为 es5 代码
}

运行 npm i babel-loader@7 babel-core babel-preset-env -D

  • 在根目录下添加.babelrc
{"presets": [
    ["@babel/preset-env", { "modules": false }]
  ]
}
  • webpack.base.conf.js
{
    test: /\.js$/,
    exclude: /(node_modules|bower_components)/,
    use: {
        loader: 'babel-loader',
        options: {
        presets: ['@babel/preset-env']
        }
    }
}
  • 在index.js添加测试代码,然后biu一下
class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    sayHello() {
        console.log(`Hello, my name is ${this.name}`);
    }
}
const person = new Person("Ben", 28);
person.sayHello();

# 八、配置路径src

  • webpack.base.conf.js