基于webpack,手写vue-cli

324 阅读6分钟
1、创建一个文件夹

新建一个文件夹my-vue-cli用来存放项目

2、初始化npm

终端输入

npm init
//npm init会生成一个pakeage.json文件,这个文件主要是用来记录这个项目的详细信息的,它会将我们在项目开发中所要用到的包,以及项目的详细信息等记录在这个项目中。方便在以后的版本迭代和项目移植的时候会更加的方便。也是防止在后期的项目维护中误删除了一个包导致的项目不能够正常运行。使用npm init初始化项目还有一个好处就是在进行项目传递的时候不需要将项目依赖包一起发送给对方,对方在接受到你的项目之后再执行npm install就可以将项目依赖全部下载到项目里。

然后回车即可,这样就能给项目创建一个npm管理环境,之后在此环境上就可以安装我们所需要的包

3、webpack、webpack-cli

安装webpack、webpack-cli

  • webpack:打包工具
  • webpack-cli:为webpack提供命令行的工具
npm i webpack webpack-cli -D
4、src、public

在根目录下新建src、public这两个文件夹,前者用来放置项目主要代码,后者用来放项目公用静态资源

  • public/index.html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>my-vue-cli</title>
</head>

<body>
    <div id="app"></div>
</body>

</html>
  • src/main.js
import {add} from './tools/add.js'
console.log("main.js",add(1,2))
  • src/tools/add.js
export const add = (a,b)=>{
    return a + b
}
5、入口文件

main.js文件就是项目的入口文件,也就相当于整个引用树的根节点,webpack打包需要从入口文件开始查找,根据引用关系,一直打包所有引用文件。

进行入口文件的配置,在根目录下新建webpack.config.js:

const path = require('path')
module.exports = {
    //模式 开发模式
    mode: 'development',
    //入口文件 main.js
    entry: {
        main: './src/main.js'
    },
    //输出
    output: {
        //输出到dist文件夹
        path: path.resolve(__dirname, './dist'),
        //js文件下
        filename: 'js/chunk-[contenthash].js',
        //每次打包前自动清除旧的dist
        clean: true
    }
}
6、配置打包命令

到package.json里配置打包命令:

 "scripts": {
    "build":"webpack"
  }

然后到终端输入 npm run build,就能发现打包成功:

image.png 但这其实不是我们想要的目的,我们的目标是将这个打包后的最终js文件,插入到刚刚的index.html中,因为js文件得让html文件引用才有意义,所以不仅要打包js,还要打包html。

loader和plugin
  • loader:加载和解析非js文件,如css、图片、ts等等
  • plugin:插件,拓展webpack打包功能,如优化体积、显示进度条等等
7、打包html

打包html需要用到html-webpack-plugin这个插件,也就是plugin,所以需要安装一下:

npm i html-webpack-plugin -D

webpack.config.js中配置一下

const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
    //刚才的代码
    //插件 plugins
    plugins: [
        new HtmlWebpackPlugin({
            //选择模板 public/index.html
            template: './public/index.html',
            //打包后的名字
            filename: 'index.html',
            //js文件插入body中
            inject: 'body'
        })
    ]
}

现在执行 npm run build 可以看到html被打包了,而且打包后的html自动引入打包后的js文件

image.png

image.png

现在打开打包后的index.html,控制台可以正常输出,说明阶段成功!

image.png

8、打包css

在src下新建styles文件夹,用来存放样式文件

  • src/styles/index.scss
body {
    background-color: #31a8fa;
}

然后我们在入口文件main.js中引入

import './styles/index.scss'

我们想要的效果是打包index.scss文件,并且让index.html自动引入打包后的css文件,所有需要安装一下几个东西:

  • sass、sass-loader:可以将scss代码转成css
  • css-loader:使webpack具有打包css的能力
  • sass-resources-loader:可选,支持打包全局公共scss文件
  • mini-css-extract-plugin:可将css代码打包成一个单独的css文件

安装以上插件

npm i sass sass-loader sass-resources-loader mini-css-extract-plugin css-loader -D

然后配置webpack.config.js

//刚才的代码...
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

module.exports = {
  //刚才的代码...
    plugins: [
    //刚才的代码...
        new MiniCssExtractPlugin({
            // 将css代码输出到dist/styles文件夹下
            filename: 'styles/chunk-[contenthash].css',
            ignoreOrder: true,
        })
    ],
    module: {
        rules: [
            {
                // 匹配文件后缀的规则
                test: /\.(css|s[cs]ss)$/,
                use: [
                    // loader执行顺序是从右到左
                    MiniCssExtractPlugin.loader,
                    'css-loader',
                    'sass-loader',
                    // {
                    //   loader: 'sass-resources-loader',
                    //   options: {
                    //     resources: [
                    //       // 放置全局引入的公共scss文件
                    //     ],
                    //   },
                    // },
                ],
            },
        ]
    }
}

此时我们重新执行打包命令 npm run build ,可以发现出现了打包后的css文件,而且index.html中自动引入了css文件:

image.png

image.png

这时看页面,可以看到body的背景已经变成蓝色,说明有效果:

image.png

9、打包图片

webpack5中已经废弃了url-loader,打包图片可以使用asset-module,我们先放置一张图片在src/assets/images中:

image.png

改一下index.scss

image.png

然后在webpack.config.js中添加打包图片的配置

 module: {
        rules: [
        //刚才的代码...
            {
                //匹配文件后缀的规则
                test: /\.(png|jpe?g|gif|svg|webp)$/,
                type: 'asset',
                parser: {
                    // 转base64的条件
                    dataUrlCondition: {
                        maxSize: 25 * 1024, // 25kb
                    }
                },
                generator: {
                    // 打包到 dist/image 文件下
                    filename: 'images/[contenthash][ext][query]',
                },
            }
        ]
    }

重新运行npm run build ,发现dist下已经有images这个文件夹了:

image.png

运行index.html,背景图已生效,说明打包成功了:

image.png

10、配置babel

babel可以将我们项目中的高级语法转化成比较低级的语法,比如可以将ES6转为ES5,这样可以兼容一些低版本浏览器,所以是很有必要的

首先安装所需的包:

  • @babel/core、babel-loader:转换语法的工具
  • @babel/preset-env:转换的一套现成规则
  • @babel/plugin-transform-runtime:转换async/await所需插件
npm i @babel/core babel-loader @babel/preset-env @babel/plugin-transform-runtime -D

由于babel是针对js文件的语法转换,所以我们需要在webpack.config.js中去针对js进行操作

module: {
        rules: [
           //刚才的代码...
            {
                //匹配js后缀文件
                test:/\.js$/,
                //排除node_modules中的js
                exclude:/node_modules/,
                use:[
                    'babel-loader'
                ]
            }
        ]
    }

单单配置了babel-loader还是不够的,我们还需要配置babel转换的规则,所以需要在根目录下创建babel.config.js

// babel.config.js

module.exports = {
    presets: [
        // 配置规则
        "@babel/preset-env"
    ],
    // 配置插件
    plugins: ["@babel/plugin-transform-runtime"]
}

此时我们重新运行打包 npm run build,可以发现打包后的js代码中,已经把刚刚代码中的ES6语法转成ES5语法了!可以看到刚刚代码中的const已经转成ES5语法了

image.png

image.png

11、打包Vue

打包Vue需要用到以下几个包:

  • vue:Vue开发所需的依赖
  • vue-loader:解析.vue文件的loader
  • vue-template-compiler:解析vue中模板的工具
  • @vue/babel-preset-jsx:支持解析vue中的jsx语法

注意:vue和vue-template-compiler版本需要一致,这里我使用2.6.14这个版本,vue-loader使用了15.9.8这个版本

先安装一下:

npm i vue@2.6.14 vue-template-compiler@2.6.14 vue-loader@15.9.8 @vue/babel-preset-jsx -D

然后需要在webpack.config.js中配置对.vue文件的解析


const {VueLoaderPlugin} = require('vue-loader')

module.exports = {
   //刚才的代码...
    plugins: [
       //刚才的代码...
        new VueLoaderPlugin()
    ],
    module: {
        rules: [
            //刚才的代码...
            {
                test:/\.vue$/,
                use:'vue-loader'
            }
        ]
    }
}

再到babel.config.js中配置一下,让webpack支持.vue文件中的jsx语法

// babel.config.js

module.exports = {
    presets: [
        // 配置规则
        "@babel/preset-env",
        //支持vue中的jsx语法
        "@vue/babel-preset-jsx"
    ],
    // 配置插件
    plugins: ["@babel/plugin-transform-runtime"]
}

现在到src下创建一个App.vue文件

<template>
    <div class="box">我是app.vue</div>
</template>

<script>
export default {}
</script>

<style lang="scss">
.box {
    width: 500px;
    height: 200px;
    color: #fff;
    background-color: #000;
}
</style>

然后改写一下src/main.js

import Vue from 'vue'
import App from './app.vue'

new Vue({
    render:(h)=>h(App)
}).$mount('#app')

此时我们重新运行 npm run build ,我们可以看看页面的效果,说明打包成功! image.png

12、配置路径别名

有时候文件引用隔着太多层,引用起来会看起来很不明确,比如 ../../../../../app.vue, 需要配置一下别名alias可以完善这个问题:

module.exports = {
   //刚才的代码...
    resolve: {
        //路径别名
        alias: {
            '@':path.resolve('./src'),
            assets:'~/assets',
            tools:'~/tools'
        },
        //引入文件时省略后缀
        extensions:['.js','.ts','.less','.vue'],
    }
}

别名配置好后:

  • 配置前:../../../../../app.vue
  • 配置后:@/app.vue
13、webpack-dev-server

我们发现,每改一次代码,就得重新打包一次,非常繁琐,有没有可以改代码后就自动重新打包的呢?这就要用到webpack-dev-server

npm i webpack-dev-server -D

到webpack.config.js中配置devServer

 devServer: {
        //自定义端口
        port: 8000,
        //自动打开浏览器
        open: true
    }

然后到package.json中配置启动命令:

 "scripts": {
    "serve":"webpack serve",
    "build": "webpack",
    "test": "echo \"Error: no test specified\" && exit 1"
  },

然后运行 npm run serve启动项目

14、区分环境

我们不能把所有配置都配置在一个webpack.config.js中,因为我们有两个环境 development(开发环境)、production(生产环境),所以我们在根目录下创建build文件夹,并创建三个文件

  • webpack.base.js:两个环境共用配置
    • 入口、输出配置
    • 各种文件的处理
    • 进度条展示
    • 路径别名
  • webpack.dev.js:开发环境独有配置
    • webpack-dev-server
    • 不同的soruce-map模式
    • 不同的环境变量
  • webapck.prod.js:生产环境独有配置
    • 不同的source-map模式
    • 不同的环境变量

我们需要安装一个合并插件,webpack-merge,用于两个环境的配置可以合并公共的配置

npm i webpack-merge -D

然后我们在根目录下新建一个build文件夹,并在此文件夹下新建webpack.base.js、webpack.dev.js、webpack.prod.js

  • webpack.base.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const { VueLoaderPlugin } = require('vue-loader')

module.exports = {
   
    // 入口文件 main.js
    entry: {
        main: './src/main.js'
    },
    // 输出
    output: {
        // 输出到 dist文件夹
        path: path.resolve(__dirname, './dist'),
        // js文件下
        filename: 'js/chunk-[contenthash].js',
        // 每次打包前自动清除旧的dist
        clean: true,
    },
    plugins: [
        new HtmlWebpackPlugin({
            // 选择模板 public/index.html
            template: './public/index.html',
            // 打包后的名字
            filename: 'index.html',
            // js文件插入 body里
            inject: 'body',
        }),
        new MiniCssExtractPlugin({
            // 将css代码输出到dist/styles文件夹下
            filename: 'styles/chunk-[contenthash].css',
            ignoreOrder: true,
        }),
        new VueLoaderPlugin()
    ],
    module: {
        rules: [
            {
                // 匹配文件后缀的规则
                test: /\.(css|s[cs]ss)$/,
                use: [
                    // loader执行顺序是从右到左
                    MiniCssExtractPlugin.loader,
                    'css-loader',
                    'sass-loader',
                    // {
                    //   loader: 'sass-resources-loader',
                    //   options: {
                    //     resources: [
                    //       // 放置全局引入的公共scss文件
                    //     ],
                    //   },
                    // },
                ],
            },
            {
                //匹配文件后缀的规则
                test: /\.(png|jpe?g|gif|svg|webp)$/,
                type: 'asset',
                parser: {
                    // 转base64的条件
                    dataUrlCondition: {
                        maxSize: 25 * 1024, // 25kb
                    }
                },
                generator: {
                    // 打包到 dist/image 文件下
                    filename: 'images/[contenthash][ext][query]',
                },
            },
            {
                //匹配js后缀文件
                test: /\.js$/,
                //排除node_modules中的js
                exclude: /node_modules/,
                use: [
                    'babel-loader'
                ]
            },
            {
                test: /\.vue$/,
                use: 'vue-loader'
            }
        ]
    },
    resolve: {
        //路径别名
        alias: {
            '@': path.resolve('./src'),
            assets: '~/assets',
            tools: '~/tools'
        },
        //引入文件时省略后缀
        extensions: ['.js', '.ts', '.less', '.vue'],
    }
}
  • webpack.dev.js
// 开发环境

const { merge } = require('webpack-merge')
const base = require('./webpack.base')

module.exports = merge(base, {
    mode: 'development',
    devServer: {
        port: 8000,
        open: true,
        // hot: true,
    }
})
  • webpack.prod.js
// 生产环境

const { merge } = require('webpack-merge')
const base = require('./webpack.base')

module.exports = merge(base, {
    mode: 'production'
})

然后到package.json修改指令:

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

接下来就可以运行这两个命令:

  • npm run build
  • npm run serve
15、构建进度条

无论是启动项目时还是打包时,都需要进度条的展示,所以需要把进度条配置在webpack.base中,我们需要先安装进度条的插件 progress-bar-webapck-plugin

npm i progress-bar-webpack-plugin -D

const ProgressBarPlugin = require('progress-bar-webpack-plugin')
const chalk = require('chalk')

module.exports = {
   
    plugins: [
        new ProgressBarPlugin({
            format: ` build [:bar] ${chalk.green.bold(':percent')} (:elapsed seconds)`,
        })
    ],
    
}

现在我们可以看到,无论是启动项目还是打包都会有进度条了

image.png

16、source-map

source-map的作用:代码报错时,能快速定位到出错位置,webpack5的所有source-map模式,可以看webpack官网: webpack.docschina.org/configurati…

其中的两种模式:

  • development:使用eval-cheap-module-source-map模式,能具体定位到源码位置和源码展示,适合开发模式,体积较小
  • production:使用nosources-source-map,只能定位源码位置,不能源码展示,体积较小,适合生产模式

开始配置source-map

  • webpack.dev.js
// 开发环境

const { merge } = require('webpack-merge')
const base = require('./webpack.base')

module.exports = merge(base, {
    mode: 'development',
    devServer: {
        port: 8000,
        open: true,
        // hot: true,
    },
    devtool:'eval-cheap-module-source-map'
})
  • webpack.prod.js
// 生产环境

const { merge } = require('webpack-merge')
const base = require('./webpack.base')

module.exports = merge(base, {
    mode: 'production',
    devtool: 'nosources-source-map'
})
17、环境变量

配置devlopment、production这两个环境的环境变量

  • webpack.dev.js
// 开发环境

const { merge } = require('webpack-merge')
const base = require('./webpack.base')
const webpack = require('webpack')

module.exports = merge(base, {
    mode: 'development',
    devServer: {
        port: 8000,
        open: true,
        // hot: true,
    },
    devtool: 'eval-cheap-module-source-map',
    plugins: [
        //定义全局变量
        new webpack.DefinePlugin({
            process: {
                env: {
                    NODE_DEV: JSON.stringify('development'),
                    // 这里可以定义你的环境变量
                    // VUE_APP_URL: JSON.stringify('https://xxx.com')
                }
            }
        })
    ]
})
  • webpack.prod.js
// 生产环境

const { merge } = require('webpack-merge')
const base = require('./webpack.base')
const webpack = require('webpack')

module.exports = merge(base, {
    mode: 'production',
    devtool: 'nosources-source-map',
    plugins: [
        //定义全局变量
        new webpack.DefinePlugin({
            NODE_DEV: JSON.stringify('prodction'),
            // 这里可以定义你的环境变量
            // VUE_APP_URL: JSON.stringify('https://xxx.com')
        })
    ]
})

参考公众号:林三心不学挖掘机