webpack4搭一个简易SPA应用

5,049 阅读7分钟

开篇

webpack 在如今的前端开发中,算是不可绕过的一个工具吧。特别是在开发SPA应用的时候,无论是开发环境,还是打包上线,都十分依赖webpack。

开发环境

win10

node -v: 10.15.0

npm -v: 6.4.1

Let's go。

体验0配置

进入到工作目录,然后创建项目

mkdir spa-webpack-demo

初始化

npm init -y

先来体验下 webpack40配置:

安装 webpack

npm i -D webpack

安装好 webpack 依赖后,创建 src 文件夹,并在 src 中新建一个 index.js

mkdir src
cd src
type nul > index.js

修改 package.json,在 scripts选项中,添加两个命令:

"dev": "webpack --mode=development",
"prod": "webpack --mode=production"

好,完事了。接下来跑命令行,测试一下

npm run dev

正常情况下,控制台会有一段如下提示,因为 webpack 命令需要依赖 webpack-cli,我们安装即可

Do you want to install 'webpack-cli' (yes/no): yes

webpack-cli安装完成之后,会自动继续跑我们的 npm run dev 指令,即可看到项目中多了一个 dist 目录,而且多了一个 main.js

接下来继续尝试 npm run prod,可以看到 dist/main.js 已被压缩。

这就是 webpack 号称的零配置,主要的工作就是定义了默认的entry路径src/index.js,定义了默认的output路径dist/main.js,然后加了一个mode参数,根据mode参数的不同帮我们添加一些预置的打包规则。

循序渐进

上述的流程里,只是体验了一把零配置的感觉,连html文件都没有,这里开始加上。

加个index.html

在根目录新建 index.html,随便编写点内容

type nul > index.html

说到处理 html 文件,肯定少不了 html-webpack-plugin, 安装它

npm i -D html-webpack-plugin

然后再项目根目录新建一个 webpack.config.js,webpack会自动使用它

type nul > webpack.config.js

webpack.config.js的内容如下

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

module.exports = {
    module: {
        rules: []
    },
    plugins: [
        new HtmlWebpackPlugin({
            filename: 'index.html',
            template: 'index.html'
        })
    ]
}

执行npm run dev,即可看到 dist文件夹多了个index.html,这个index.html自动引入了打包后的dist/main.js

加个本地服务器

index.html 是生成了,可总不能每次手动打开它在浏览器里面预览吧, webpack 官方推荐我们用 webpack-dev-server做服务器,安装它

npm i -D webpack-dev-server

安装成功后, 修改webpack.config.js,添加 devServer 选项 和 webpack.HotModuleReplacementPlugin 插件。

对于文件中已经添加过的内容,后面我都会用注释表示。

const path = require('path');
const webpack = require('webpack');

function resolve(dir) {
    return path.join(__dirname, './', dir)
}

module.exports = {
    // module - 略
    devServer: {
        contentBase: resolve('dist'), // 根目录
        hot: true,   // 是否开启热替换,无须手动刷新浏览器
        port: 8081,    // 端口
        open: true,     // 是否自动打开浏览器
        noInfo: true   // 不提示打包信息,错误和警告仍然会显示
    },
    plugins: [
        new webpack.HotModuleReplacementPlugin(),
        // HtmlWebpackPlugin - 略
    ]
}

然后修改 package.json scriptsdev 选项

"dev": "webpack-dev-server --mode=development",

注意:当 devServerhot参数为true时,记得要在插件里添加new webpack.HotModuleReplacementPlugin(), 或者你可以在命令行中带上hot参数,这样就不需要自己再往plugins中添加插件了。

"dev": "webpack-dev-server --hot --mode=development"

然后npm run dev,就可以尝试静态资源热替换功能了。

处理js, css 等其他静态资源

首先我们要清楚一点,webpack 它本身是不知道应该如何处理静态资源的,但是它提供了loaderplugin机制。

loader 的作用,顾名思义:加载器,就是匹配到的静态资源,都要经过loader的内部处理,再返回处理之后的结果。我觉得,loader像是一个拦截器。

说到js,我们会想到 babel-loaderbabel-loader是干吗的?常规操作是,将匹配到的js文件的ES6代码 根据 babelrc文件内的配置 编译成对应的 ES5代码。

我们这里先添加一个.babelrc文件

新增.babelrc文件

type nul > .babelrc

编辑 .babelrc 内容

{
    // 预设置,告诉Babel要转换的源码使用了哪些新的语法特性
    // targets, useBuiltIns 等选项用于编译出兼容目标环境的代码
    // 其中 useBuiltIns 如果设为 "usage"
    // Babel 会根据实际代码中使用的 ES6/ES7 代码,以及与你指定的 targets,按需引入对应的 polyfill
    // 而无需在代码中直接引入 import '@babel/polyfill',避免输出的包过大,同时又可以放心使用各种新语法特性。
    "presets": [
        ["@babel/preset-env", {
            // modules 是否 将 ES6 的 import/export模块化 转为 babel 的 CommonJs 规范模块化
            "modules": false,
            "targets": {
                // "> 1%" : 支持市场份额超过1%的浏览器, 
                // ""last 2 versions"": 支持每个浏览器最后两个版本
                // "not ie <= 8": 在之前条件的浏览器中,排除 IE8 以下的浏览器
                "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
            },
            "useBuiltIns": "usage"
        }]
    ],
    // 所用插件
    // transform-runtime 插件表示不管浏览器是否支持ES6,只要是ES6的语法,它都会进行转码成ES5
    // 这个是需要优化的
    "plugins": ["@babel/plugin-transform-runtime"]
}

安装 babel 依赖,注意:

babel 7+ 已经废弃了presets 中 stage-x 的用法,改为在plugins中添加。并且应用了npm scope包,代码全部在 @babel 中,避免以前那种 babel-preset-xxx, babel-plugin-xxx 的用法

最新的 babel-loader 版本是8+,需要依赖 babel-core 版本7+,包名为 @babel/core, 版本6+的包名为 babel-core

再分析上面的 .babelrc 文件,它用到了@babel/preset-env, @babel/plugin-transform-runtime, 这些依赖都要我们安装好

如果使用了 @babel/preset-env,则不支持在 plugins 中 添加 stage-x

npm i -D babel-loader @babel/core @babel/preset-env @babel/plugin-transform-runtime

谈到css,我们就应该 想到 style-loadercss-loader。先安装它们

npm i -D style-loader css-loader

再安装 url-loader 用于解析静态资源,如图片,字体等

npm i -D url-loader

然后修改 webpack.config.jsrules, 添加如下代码

module.exports = {
    module: {
        rules: [
            {
                test: /\.js$/,
                loader: 'babel-loader', 
                include: [
                    resolve('src'),
                    resolve('node_modules/webpack-dev-server/client')
                ]
            },
            {
                test: /\.css$/,
                use: ['style-loader', 'css-loader']
            },
            {
                test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
                loader: 'url-loader',
                exclude: [],
                options: {
                    limit: 10000,
                    name: 'img/[name].[hash:7].[ext]'
                }
            }
        ]
    },
    // devServer - 略
    // plugins - 略
}
接下来准备开发了,用 vue 吧。

vue 就不用装在 devDependencies 中了。

npm i -S vue 
// vue-loader 依赖 vue-template-compiler 和 vue-style-loader
npm i -D vue-loader vue-template-compiler vue-style-loader

修改 webpack.config.js, 添加 如下代码

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

module.exports = {
    resolve: {
        extensions: ['.js', '.vue', '.json'],
        alias: {
            '@': resolve('src')
        }
    },
    module: {
        rules: [
            {
                test: /\.vue$/,
                loader: 'vue-loader'
            },
            
            // 其他 - 略
        ]
    },
    // devServer - 略
    plugins: [
        // 加在最前面
        new VueLoaderPlugin()
        
        // 其他 - 略
    ]
}

src 下 新建一个 views 目录 和 assets 目录,

我在 assets 目录下,增加了一个 logo.png 文件

views下创建一个 myTest 组件,myTest/index.vue, 编辑 index.vue

<template>
    <div>
        <i class="logo"></i>
    </div>
</template>

<script>
export default {
    name: 'myTest'
}
</script>

<style scoped>
    .logo {
        display: block;
        margin: auto;
        width: 400px;
        height: 400px;
        background: url(../../assets/logo.png);
    }
</style>

src 目录下新建一个 App.vue, 内容如下

<template>
  <div id="app">
    <my-test></my-test>
  </div>
</template>

<script>
import myTest from "./views/myTest/index";

export default {
  name: "App",
  components: {
    myTest
  }
};
</script>

编辑 src 目录下的 index.js,内容如下:

import Vue from 'vue';

import App from './App';

new Vue({
    el: '#app',
    render: h => h(App)
})

最后npm run dev,查看效果。

锦上添花

添加 打包进度条 信息,如下
npm i -D progress-bar-webpack-plugin

修改 webpack.config.js

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

// ....

plugins: [
    // 其他 - 略
    new ProgressBarPlugin()
]
添加 打包结果消息通知
npm i -D webpack-build-notifier

修改 webpack.config.js

const WebpackBuildNotifierPlugin = require('webpack-build-notifier');

// ....

plugins: [
    // 其他 - 略
    new WebpackBuildNotifierPlugin()
]
归类打包信息
npm i -D webpack-dashboard

修改 webpack.config.js

const DashboardPlugin = require('webpack-dashboard/plugin');

// ....

plugins: [
    // 其他 - 略
    new DashboardPlugin()
]

修改 package.json

"dev": "webpack-dashboard -- webpack-dev-server --mode=development"

这个我使用了,感觉效果不是很理想啊,会新开一个窗口,而且还不能滚动查看信息,不清楚是不是哪里用错了。

效果如图:

整个代码结构如图:

尚未完成的功能

  1. production 环境,需要使用 mini-css-extract-pluginoptimize-css-assets-webpack-plugin 插件,抽离并优化 css 文件
  2. production 环境,需要使用 UglifyJsPlugin 插件,压缩 js 文件,这个插件允许多核编译
  3. production 环境,需要使用 optimization 选项 splitChunks
  4. vue-routervuex 的引入
  5. 等等

写在最后

希望本文的流程能帮助到有需要的读者,另外本文的打包功能实现的比较粗糙,打包速度比较慢,如果看官有啥建议,请在评论下告知下我。

如果有错误的地方,还请指出。谢谢阅读。

代码地址 spa-webpack-demo

参考

babel官方文档

Webpack4 +babel7 多入口配置详细教程