Webpack4构建项目(初级)

本文学习用webpack4来构建一个vue项目。

1. 初始化项目

新建文件夹,控制台进入文件目录,执行:npm init ,一路回车,会生成一个package.json文件。

2. 全局安装webpack

全局安装:

npm install webpack webpack-cli vue -g 
//如果是mac,需要以管理员身份运行: sudo npm install webpack webpack-cli vue -g

全局安装完成后,执行本地安装命令:

npm install webpack webpack-cli vue vue-loader webpack-dev-server

3. 安装依赖

安装常用依赖包:vue-template-compiler css-loader file-loader style-loader sass-loader url-loader html-webpack-plugin cross-env

npm install vue-template-compiler css-loader file-loader style-loader sass-loader url-loader html-webpack-plugin cross-env
  • vue-template-compiler
    将vue2.0模板预编译为渲染函数(template => ast => render),以避免运行时编译开销和csp限制,与vue-loader一起使用。

  • css-loader、style-loader
    webpack打包只处理js之间的依赖关系,如果js中引入了css文件,就需要css-loader来识别这个模块。css文件经过css-loader处理后,会导出一个包含style样式的js数组,style-loader的作用是将这些样式内容挂载到html页面上,使其生效。

  • html-webpack-plugin
    最常用的插件,可为html文件中引入的外部资源如script、link动态添加每次更新后的hash;可生成html入口文件,比如单页面上可以生成一个html文件入口,配置N个html-webpack-plugin则可以生成N个页面入口。

  • cross-env
    跨平台设置和使用环境变量。

4. 编写webpack配置文件

在目录下创建一个webpack.config.js文件,编写配置:

const path = require('path');//nodejs中的基本包,处理文件路径
const VueLoaderPlugin = require('vue-loader/lib/plugin');//vue-loader需要配合此插件使用
const HtmlWebpackPlugin = require('html-webpack-plugin');

const config = {
    target: 'web',
    entry:path.join(__dirname, 'src/index.js'),
    output: {
        filename: 'bundle.js',
        path: path.join(__dirname,'dist')
    },
    module: {
        rules: [
            {
                test: /\.vue$/,
                use: 'vue-loader'
            },
            {
                test: /\.(gif|jpg|jpeg|png|svg)/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 1024,
                            name: '[name].[ext]'
                        }
                    }
                ]
            }
        ]
    },
    plugins: [
        new VueLoaderPlugin(),
        new HtmlWebpackPlugin()
    ]
}

module.exports = config

5. 区分开发环境和生产环境

package.json中增加两行配置:
"build": "cross-env NODE_ENV=production webpack --config webpack.config.js",//生产环境
"dev": "cross-env NODE_ENV=development webpack-dev-server --config webpack.config.js",//开发环境

{
  "name": "vuetest",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "cross-env NODE_ENV=production webpack --config webpack.config.js",
    "dev": "cross-env NODE_ENV=development webpack-dev-server --config webpack.config.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "cross-env": "^7.0.2",
    "css-loader": "^4.2.0",
    "extract-text-webpack-plugin": "^3.0.2",
    "file-loader": "^6.0.0",
    "html-webpack-plugin": "^4.3.0",
    "mini-css-extract-plugin": "^0.9.0",
    "postcss-loader": "^3.0.0",
    "sass-loader": "^9.0.2",
    "style-loader": "^1.2.1",
    "url-loader": "^4.1.0",
    "vue": "^2.6.11",
    "vue-loader": "^15.9.3",
    "vue-template-compiler": "^2.6.11",
    "webpack": "^4.44.1",
    "webpack-cli": "^3.3.12"
  }
}

webpack.config.js:

const path = require('path');//nodejs中的基本包,处理文件路径
const VueLoaderPlugin = require('vue-loader/lib/plugin');//vue-loader需要配合此插件使用
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');

const isDev = process.env.NODE_ENV === 'development'

const config = {
    target: 'web',
    entry:path.join(__dirname, 'src/index.js'),
    output: {
        filename: 'bundle.js',
        path: path.join(__dirname,'dist')
    },
    module: {
        rules: [
            {
                test: /\.vue$/,
                use: 'vue-loader'
            },
            {
                test: /\.(gif|jpg|jpeg|png|svg)/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 1024,
                            name: '[name].[ext]'
                        }
                    }
                ]
            }
        ]
    },
    plugins: [
        new VueLoaderPlugin(),
        new HtmlWebpackPlugin(),
        //通过配置了DefinePlugin,那么这里面的标识就相当于全局变量,便可以在src目录下直接取到当前环境变量。
        new webpack.DefinePlugin({
            'process.env': {
                NODE_ENV: isDev ? '"development"' : '"production"'
            }
        })
    ]
}

//开发环境
if(isDev){
    config.devtool = '#cheap-module-eval-source-map' // 调试代码时可以看到自己原本的代码,而不是编译后的
    config.devServer = {
        port: 8000,
        host: '0.0.0.0',
        overlay: {
            errors: true // 将webpack编译的错误显示在网页上面
        },
        open: true // 在启用webpack-dev-server时,自动打开浏览器
    }
    config.plugins.push(
        new webpack.HotModuleReplacementPlugin(),//模块热替换
        new webpack.NoEmitOnErrorsPlugin()
    )
}

module.exports = config

6. 编写源文件

新建src目录,在src目录下新建app.vue和index.js。
app.vue:

<template>
   <div>{{text}}</div>
</template>

<script>
export default {
    data() {
       return {
          text: 'abc'
       }
    }
}
</script>

<style>

</style>

index.js:

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

const root = document.createElement('div');
document.body.appendChild(root);

new Vue({
    render: (h) => h(App)
}).$mount(root)

尝试执行npm run buildnpm run dev查看效果,webpack会自动生成dist目录,将业务代码和类库代码打包成dist目录下的bundle.js文件。

7. 区分开发环境和生产环境的打包配置,在生产环境下,用mini-css-extract-plugin来分离css

通常,webpack将css内容都打包在bundle.js里,插件mini-css-extract-plugin能够将css和js模块分开打包,把css代码从js文件中抽离出来,单独出一个模块。
执行npm install mini-css-extract-plugin
修改webpack.config.js:

const path = require('path');//nodejs中的基本包,处理文件路径
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const miniCssExtractPlugin = require('mini-css-extract-plugin');

const isDev = process.env.NODE_ENV === 'development'

const config = {
    target: 'web',
    entry:path.join(__dirname, 'src/index.js'),
    output: {
        filename: 'bundle.js',
        path: path.join(__dirname,'dist')
    },
    module: {
        rules: [
            {
                test: /\.vue$/,
                use: 'vue-loader'
            },
            {
                test: /\.(gif|jpg|jpeg|png|svg)/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 1024,
                            name: '[name].[ext]'
                        }
                    }
                ]
            }
        ]
    },
    // process.env.NODE_ENV = develpment
    plugins: [
        new VueLoaderPlugin(),
        new HtmlWebpackPlugin(),
        new webpack.DefinePlugin({
            'process.env': {
                NODE_ENV: isDev ? '"development"' : '"production"'
            }
        })
    ]
}

//判断正式环境和开发环境
if (isDev) {
    config.module.rules.push(
        {
            test: /\.css$/,
            use: [
                'style-loader',
                'css-loader'
            ]
        },
        {
            test: /\.scss$/,
            use: [
                'style-loader',
                'css-loader',
                'sass-loader'
            ]
        },
    )
    config.devtool = '#cheap-module-eval-source-map'
    config.devServer = {
        port: 8000,
        host: '0.0.0.0',
        overlay: {
            errors: true,
        },
        hot: true
    }
    config.plugins.push(
        new webpack.HotModuleReplacementPlugin(),
        new webpack.NoEmitOnErrorsPlugin()
    )
} else {
    config.module.rules.push(
        {
            test: /\.css$/,
            use: [
                miniCssExtractPlugin.loader,//注意:miniCssExtractPlugin与style-loader冲突,使用miniCssExtractPlugin时不要使用style-loader,否则会报错
               // 'style-loader',
                'css-loader'
            ]
        },
        {
            test: /\.scss$/,
            use: [
                miniCssExtractPlugin.loader,//注意:miniCssExtractPlugin与style-loader冲突,使用miniCssExtractPlugin时不要使用style-loader,否则会报错
               // 'style-loader',
                'css-loader',
                'sass-loader'
            ]
        }
    )
    config.plugins.push(
        new miniCssExtractPlugin({
            filename: 'css/[name].[contenthash].css',
            allChunks: true
        }),
    )
}

module.exports = config

8. 区分打包类库代码

bundle.js包含了类库代码和业务代码,每次更新业务代码,都生成新的bundle.js,意味着浏览器中缓存的bundle.js也得更新。
我们期望类库代码能够长时间地在浏览器中缓存,而不必随着业务代码的更新而更新。
使用webpack.optimization.splitChunks分割类库代码,webpack4中可直接使用,无需安装任何插件,修改后的配置文件如下:

const path = require('path');//nodejs中的基本包,处理文件路径
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const miniCssExtractPlugin = require('mini-css-extract-plugin');

const isDev = process.env.NODE_ENV === 'development'

const config = {
    target: 'web',
    entry:path.join(__dirname, 'src/index.js'),
    output: {
        filename: 'bundle.js',
        path: path.join(__dirname,'dist')
    },
    module: {
        rules: [
            {
                test: /\.vue$/,
                use: 'vue-loader'
            },
            {
                test: /\.(gif|jpg|jpeg|png|svg)/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 1024,
                            name: '[name].[ext]'
                        }
                    }
                ]
            }
        ]
    },
    // process.env.NODE_ENV = develpment
    plugins: [
        new VueLoaderPlugin(),
        new HtmlWebpackPlugin(),
        new webpack.DefinePlugin({
            'process.env': {
                NODE_ENV: isDev ? '"development"' : '"production"'
            }
        })
    ]
}

//判断正式环境和开发环境
if (isDev) {
    config.module.rules.push(
        {
            test: /\.css$/,
            use: [
                'style-loader',
                'css-loader'
            ]
        },
        {
            test: /\.scss$/,
            use: [
                'style-loader',
                'css-loader',
                'sass-loader'
            ]
        },
    )
    config.devtool = '#cheap-module-eval-source-map'
    config.devServer = {
        port: 8000,
        host: '0.0.0.0',
        overlay: {
            errors: true,
        },
        hot: true
    }
    config.plugins.push(
        new webpack.HotModuleReplacementPlugin(),
        new webpack.NoEmitOnErrorsPlugin()
    )
} else {
    config.entry = {
        app:path.join(__dirname, 'src/index.js')
    }
    config.optimization = {
        splitChunks : {
            chunks: 'all',
            minSize: 30000,
            maxSize: 0,
            minChunks: 1,
            maxAsyncRequests: 5,
            maxInitialRequests: 3,
            automaticNameDelimiter: '~',
            name: true,
            cacheGroups: {
                vendors: {
                    test: /[\\/]node_modules[\\/]/,
                    priority: -10
                },
                default: {
                    minChunks: 2,
                    priority: -20,
                    reuseExistingChunk: true
                }
            }
        }
    },
    config.output.filename = '[name].[chunkhash:8].js'
    config.module.rules.push(
        {
            test: /\.css$/,
            use: [
                miniCssExtractPlugin.loader,//注意:miniCssExtractPlugin与style-loader冲突,使用miniCssExtractPlugin时不要使用style-loader,否则会报错
                //'style-loader',
                'css-loader'
            ]
        },
        {
            test: /\.scss$/,
            use: [
                miniCssExtractPlugin.loader,//注意:miniCssExtractPlugin与style-loader冲突,使用miniCssExtractPlugin时不要使用style-loader,否则会报错
               // 'style-loader',
                'css-loader',
                'sass-loader'
            ]
        }
    )
    config.plugins.push(
        new miniCssExtractPlugin({
            filename: 'css/[name].[contenthash].css',
            allChunks: true
        }),
    )
}

module.exports = config

执行npm run build,dist目录下将生成业务代码app.js文件和类库代码vendors.js。

多次打包,会发现dist目录下生成了多个hash值不同的vendor.js和app.js,这时我们需要安装插件,在每次打包前清除dist目录。

npm install clean-webpack-plugin

webpack.config.js :

const path = require('path');//nodejs中的基本包,处理文件路径
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const miniCssExtractPlugin = require('mini-css-extract-plugin');
// 引入打包时清除 dist 目录的插件,引入时需要用对象{ CleanWebpackPlugin }包裹起来
const {CleanWebpackPlugin} = require('clean-webpack-plugin');

const isDev = process.env.NODE_ENV === 'development'

const config = {
    target: 'web',
    entry:path.join(__dirname, 'src/index.js'),
    output: {
        filename: 'bundle.js',
        path: path.join(__dirname,'dist')
    },
    resolve: {
        alias: {
            '@': path.resolve(__dirname,'./src'),
        }
    },
    module: {
        rules: [
            {
                test: /\.vue$/,
                use: 'vue-loader'
            },
            {
                test: /\.(gif|jpg|jpeg|png|svg)/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 1024,
                            name: '[name].[ext]'
                        }
                    }
                ]
            }
        ]
    },
    // process.env.NODE_ENV = develpment
    plugins: [
        new VueLoaderPlugin(),
        new HtmlWebpackPlugin(),
        new webpack.DefinePlugin({
            'process.env': {
                NODE_ENV: isDev ? '"development"' : '"production"'
            }
        })
    ]
}

//判断正式环境和开发环境
if (isDev) {
    config.module.rules.push(
        {
            test: /\.css$/,
            use: [
                'style-loader',
                'css-loader'
            ]
        },
        {
            test: /\.scss$/,
            use: [
                'style-loader',
                'css-loader',
                'sass-loader'
            ]
        },
    )
    config.devtool = '#cheap-module-eval-source-map'
    config.devServer = {
        port: 8000,
        host: '0.0.0.0',
        overlay: {
            errors: true,
        },
        hot: true
    }
    config.plugins.push(
        new webpack.HotModuleReplacementPlugin(),
        new webpack.NoEmitOnErrorsPlugin()
    )
} else {
    config.entry = {
        app:path.join(__dirname, 'src/index.js')
    }
    config.optimization = {
        splitChunks : {
            chunks: 'all',
            minSize: 30000,
            maxSize: 0,
            minChunks: 1,
            maxAsyncRequests: 5,
            maxInitialRequests: 3,
            automaticNameDelimiter: '~',
            name: true,
            cacheGroups: {
                vendors: {
                    test: /[\\/]node_modules[\\/]/,
                    priority: -10
                },
                default: {
                    minChunks: 2,
                    priority: -20,
                    reuseExistingChunk: true
                }
            }
        }
    },
    config.output.filename = '[name].[chunkhash:8].js'
    config.module.rules.push(
        {
            test: /\.css$/,
            use: [
                miniCssExtractPlugin.loader,
                //'style-loader',
                'css-loader'
            ]
        },
        {
            test: /\.scss$/,
            use: [
                miniCssExtractPlugin.loader,
                //'style-loader',
                'css-loader',
                'sass-loader'
            ]
        }
    )
    config.plugins.push(
        new miniCssExtractPlugin({
            filename: 'css/[name].[contenthash].css',
            allChunks: true
        }),
        // 打包时,把 dist 目录下的文件内容先清除
        new CleanWebpackPlugin()
    )
}

module.exports = config

9. 用babel将es6转译为浏览器能够识别的代码

安装相应的包:

npm install -D babel-loader @babel/core @babel/preset-env webpack

创建一个与webpack.config.js同级的文件.babelrc,写入内容:

{
    "presets" : [
       "@babel/preset-env"
    ]
}

webpack.config.js:

const path = require('path');//nodejs中的基本包,处理文件路径
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const miniCssExtractPlugin = require('mini-css-extract-plugin');
// 引入打包时清除 dist 目录的插件,引入时需要用对象{ CleanWebpackPlugin }包裹起来
const {CleanWebpackPlugin} = require('clean-webpack-plugin');

const isDev = process.env.NODE_ENV === 'development'

const config = {
    target: 'web',
    entry:path.join(__dirname, 'src/index.js'),
    output: {
        filename: 'bundle.js',
        path: path.join(__dirname,'dist')
    },
    resolve: {
        alias: {
            '@': path.resolve(__dirname,'./src'),
        }
    },
    module: {
        rules: [
            {
                test: /\.vue$/,
                use: 'vue-loader'
            },
            {
                test: /\.(gif|jpg|jpeg|png|svg)/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 1024,
                            name: '[name].[ext]'
                        }
                    }
                ]
            },
            {
                test: /\.js$/,
                exclude: /node_modules/,
                loader: "babel-loader"
            }
        ]
    },
    // process.env.NODE_ENV = develpment
    plugins: [
        new VueLoaderPlugin(),
        new HtmlWebpackPlugin(),
        new webpack.DefinePlugin({
            'process.env': {
                NODE_ENV: isDev ? '"development"' : '"production"'
            }
        })
    ]
}

//判断正式环境和开发环境
if (isDev) {
    config.module.rules.push(
        {
            test: /\.css$/,
            use: [
                'style-loader',
                'css-loader'
            ]
        },
        {
            test: /\.scss$/,
            use: [
                'style-loader',
                'css-loader',
                'sass-loader'
            ]
        },
    )
    config.devtool = '#cheap-module-eval-source-map'
    config.devServer = {
        port: 8000,
        host: '0.0.0.0',
        overlay: {
            errors: true,
        },
        hot: true
    }
    config.plugins.push(
        new webpack.HotModuleReplacementPlugin(),
        new webpack.NoEmitOnErrorsPlugin()
    )
} else {
    config.entry = {
        app:path.join(__dirname, 'src/index.js')
    }
    config.optimization = {
        splitChunks : {
            chunks: 'all',
            minSize: 30000,
            maxSize: 0,
            minChunks: 1,
            maxAsyncRequests: 5,
            maxInitialRequests: 3,
            automaticNameDelimiter: '~',
            name: true,
            cacheGroups: {
                vendors: {
                    test: /[\\/]node_modules[\\/]/,
                    priority: -10
                },
                default: {
                    minChunks: 2,
                    priority: -20,
                    reuseExistingChunk: true
                }
            }
        }
    },
    config.output.filename = '[name].[chunkhash:8].js'
    config.module.rules.push(
        {
            test: /\.css$/,
            use: [
                miniCssExtractPlugin.loader,
                //'style-loader',
                'css-loader'
            ]
        },
        {
            test: /\.scss$/,
            use: [
                miniCssExtractPlugin.loader,
                //'style-loader',
                'css-loader',
                'sass-loader'
            ]
        }
    )
    config.plugins.push(
        new miniCssExtractPlugin({
            filename: 'css/[name].[contenthash].css',
            allChunks: true
        }),
        // 打包时,把 dist 目录下的文件内容先清除
        new CleanWebpackPlugin()
    )
}

module.exports = config
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345