金沙官网线上webpack4 系列教程(四): 单页面解决方

本节课讲解webpack4打包单页应用过程中的代码分割和代码懒加载。不同于多页面应用的提取公共代码,单页面的代码分割和懒加载不是通过webpack配置来实现的,而是通过webpack的写法和内置函数实现的。

1.导语

目前webpack针对此项功能提供 2 种函数:

首先来简单介绍一下webpack:现代 JavaScript 应用程序的 静态模块打包工具 。当 webpack 处理应用程序时,它会在内部构建一个会映射项目所需的每个模块 的依赖图,并生成一个或多个 bundle 。webpack4.0出现之后,我们可以不用再引入一个配置文件来打包项目,并且它仍然有着很高的可配置性,可以很好满足我们的需求。 在开始正文之前,首先先来看看我们要实现的成果:

  1. import(): 引入并且自动执行相关 js 代码
  2. require.ensure(): 引入但需要手动执行相关 js 代码

支持ES6+JQuery+Less/Scss的单页/多页脚手架 支持ES6+React+Less/Scss+Typescript的单页/多页脚手架 支持ES6+Vue+Less/Scss+Typescript的单页/多页脚手架

本文将会进行逐一讲解。

基于webpack4.0搭建的脚手架(支持react/vue/typescript/es6+/jquery+less/scss)

>>> 本节课源码

在脚手架的开发过程中我会详细介绍每个插件或者loader的用途以及webpack的核心理念,如有不懂或者有其他更好的想法欢迎交流。 下面是基于该文章的webpack4.0的思维导图:

>>> 所有课程源码

2.webpack核心概念

1. 准备工作

此次代码的目录结构如下:

金沙官网线上 1

其中,page.js是入口文件,subPageA.jssubPageB.js共同引用module.js。下面,我们按照代码引用的逻辑,从底向上展示代码:

module.js:

export default "module";

subPageA.js:

import "./module";
console.log("I'm subPageA");
export default "subPageA";

subPageB.js:

import "./module";
console.log("I'm subPageB");
export default "subPageB";

请注意:subPageA.js 和 subPageB.js 两个文件中都执行了console.log()语句。之后将会看到import()require()不同的表现形式:是否会自动执行 js 的代码?

入口:指示 webpack 应该使用哪个模块作为入口起点 输出:告诉 webpack 在哪里输出它所创建的 bundle,以及如何命名这些文件 loader:让 webpack 能够去处理其他类型的文件,并将它们转换为有效模块,以供应用程序使用 插件:用于执行范围更广的任务。包括:打包优化,资源管理,注入环境变量 模式:通过选择 development, production 或 none 之中的一个,来设置 mode 参数,从而进行不同的打包优化 浏览器兼容性:支持所有符合ES5 标准的浏览器下面提供官网的打包模型

2. 编写配置文件

下面编写webpack配置文件(很简单):

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

module.exports = {
  entry: {
    page: "./src/page.js" //
  },
  output: {
    publicPath: __dirname + "/dist/",
    path: path.resolve(__dirname, "dist"),
    filename: "[name].bundle.js",
    chunkFilename: "[name].chunk.js"
  }
};

同时,关于第三方库,因为要在page.js中使用lodash,所以,package.json文件配置如下:

{
  "devDependencies": {
    "webpack": "^4.15.1"
  },
  "dependencies": {
    "lodash": "^4.17.10"
  }
}

3.支持ES6+JQuery+Less/Scss的单页/多页脚手架

金沙官网线上,3. import()编写page.js

我个人是非常推荐import()写法,因为和 es6 语法看起来很像。除此之外,import()可以通过注释的方法来指定打包后的 chunk 的名字。

除此之外,相信对vue-router熟悉的朋友应该知道,其官方文档的路由懒加载的配置也是通过import()来书写的。

下面,我们将书写page.js:

import(/* webpackChunkName: 'subPageA'*/ "./subPageA").then(function(subPageA) {
  console.log(subPageA);
});

import(/* webpackChunkName: 'subPageB'*/ "./subPageB").then(function(subPageB) {
  console.log(subPageB);
});

import(/* webpackChunkName: 'lodash'*/ "lodash").then(function(_) {
  console.log(_.join(["1", "2"]));
});
export default "page";

命令行中运行webpack,打包结果如下:
金沙官网线上 2

我们创建index.html文件,通过<script>标签引入我们打包结果,需要注意的是:因为是单页应用,所以只要引用入口文件即可(即是上图中的page.bundle.js)。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <script src="./dist/page.bundle.js"></script>
</body>
</html>

打开浏览器控制台,刷新界面,结果如下图所示:

金沙官网线上 3

图中圈出的部分,就是说明import()会自动运行subPageA.js和subPageB.js的代码。

在 NetWork 选项中,我们可以看到,懒加载也成功了:

金沙官网线上 4

在实现脚手架之前,假设我们已经创建了目录和package.json文件,接下来先安装webpack相关依赖:

4. require()编写page.js

require.ensure()不会自动执行js代码,请注意注释:

require.ensure(
  ["./subPageA.js", "./subPageB.js"], // js文件或者模块名称
  function() {
    var subPageA = require("./subPageA"); // 引入后需要手动执行,控制台才会打印
    var subPageB = require("./subPageB");
  },
  "subPage" // chunkName
);

require.ensure(
  ["lodash"],
  function() {
    var _ = require("lodash");
    _.join(["1", "2"]);
  },
  "vendor"
);

export default "page";

其实,根据我们编写的代码,subPageA.jssubPageB.js共同引用了module.js文件,我们可以将module.js体现抽离出来:

require.include("./module.js"); // 将subPageA和subPageB共用的module.js打包在此page中

// ...
// 再输入上面那段代码

最终打包后,检验和引入方法与import()一致,这里不再冗赘。


欢迎技术交流,引用请注明出处。
个人网站:董沅鑫的个人网站
原文链接:webpack4 系列教程(四): 单页面解决方案--代码分割和懒加载

// 此处建议安装局部依赖,安装全局依赖可能会出现版本问题npm install -D webpack webpack-cli

因为项目要支持es6+,我们还需要安装babel相关依赖:

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

这个时候可以开始配置我们的脚手架逻辑了,为了项目结构清晰易于维护,我们建一个build目录专门放webpack构建的脚本,webpack默认的配置文件是webpack.config.js,由于实际项目需要,我们将其拆分为3个文件,分别是webpack通用配置文件webpack.base.js,开发环境配置文件webpack.dev.js以及生产环境配置文件webpack.prod.js。

我们先处理webpack.common.js文件

const path = require;const webpack = require;module.exports = { entry: { main: './src/index.js', }, output: { path: path.resolve, }, module: { rules: [ // 将es6编译成es5 { test: /.jsx?$/, // ?表示x有0个或一个 exclude: /node_modules/, // 不编译某个目录下的文件 include: path.resolve, // 只在include包含的目录下进行loader编译 use: [ "babel-loader", ] }, ] }}

为了项目后期的开发和维护,我们建立好项目结构:

public目录是事先准备好的html模版,这里就不介绍了,其他目录可根据具体项目进行设置。

我们还需要一个插件将打包后的文件植入到html模版中并导出到dist目录下,这里使用html-webpack-plugin来实现

npm install -D html-webpack-plugin

现在webpack.base.js为如下:

const path = require;const HtmlWebpackPlugin = require('html-webpack-plugin');const template = path.resolve(__dirname, '../public/index.html');module.exports = { entry: { main: './src/index.js' }, output: { path: path.resolve }, module: { rules: [ // 将es6编译成es5 { test: /.jsx?$/, // ?表示x有0个或一个 exclude: /node_modules/, // 不编译某个目录下的文件 include: path.resolve, // 只在include包含的目录下进行loader编译 use: [ "babel-loader", ] }, ] }, plugins: [ new HtmlWebpackPlugin({ template, filename: 'index.html' }) ]}

为了打包项目,我们需要在webpack.prod.js目录下进行配置,此处需要一个模块webpack-merge将wepack基础配置合并进生产配置,我们先来安装一下:

npm install -D webpack-merge

webpack.prod.js配置如下:

const merge = require;const base = require;module.exports = merge({ mode: 'production', output: { filename: 'js/[name]_[contenthash].js', // 入口和内容hash组成的文件名,也可以是hashchunkFilename: 'js/[name]_[contenthash].chunk.js' }}, base)

然后在package.json里添加执行脚本:

"scripts": { "build": "webpack --config ./build/webpack.prod.js" }

webpack默认会找名为webpack.config.js的文件,由于我们将其拆解为prod和dev,所以我们要手动指定webpack执行的文件,添加--config,即可手动指定目录。下面我们开始写一段代码试试吧,在index.js中写入如下es6代码:

// index.jslet name = 'xuxi';let say =  => { alert;

npm run build

此时我们会看见项目中多了一个dist目录,里面的html也植入了相应的代码,

ok,第一步完成。 下一步是支持css,我们先安装如下几个模块:

npm install --save-dev css-loader style-loader

在webpack.base.js中的module中添加如下代码:

module: { rules: [ // 将es6编译成es5 { test: /.jsx?$/, // ?表示x有0个或一个 exclude: /node_modules/, // 不编译某个目录下的文件 include: path.resolve, // 只在include包含的目录下进行loader编译 use: [ "babel-loader", ] }, // 加载css { test: /.css$/, use: ['style-loader', 'css-loader'], }, ] }

注意,laoder的加载顺序是从下往上,从右往左的,所以配置loader的时候要注意一下顺序。 此时在styles目录下加入app.css,在js中引入:

// app.css#root { background-color: #f0c; height: 100px;}// index.jsimport './styles/app.css'

此时打开浏览器,可以看到css生效了:

现在css导入虽然生效了,但是是有js动态创建添加到head里面的,如果后期项目复杂了,将会严重影响项目的加载速度,所以我们这里需要另一个插件,对css进行代码分割,单独生成css文件:

npm isntall mini-css-extract-plugin -D

根据该插件的官方配置,我们需要把style-loader替换成该插件提供的loader,并配置导出的css文件目录和文件名,为了提高开发环境构建速度,我们只在生产环境分割css:

// webpack.prod.jsconst merge = require;const MiniCssExtractPlugin = require('mini-css-extract-plugin');const base = require;module.exports = merge({ mode: 'production', output: { filename: 'js/[name]_[contenthash].js', // 入口和内容hash组成的文件名,也可以是hashchunkFilename: 'js/[name]_[contenthash].chunk.js' }, module: { rules: [ { test: /.css$/, use: [ // loader解析的顺序是从下到上,从右到左的顺序 { loader: MiniCssExtractPlugin.loader, options: { filename: '[name].css', chunkFilename: '[name].css', publicPath: '../' //****最后打包的时候替换引入文件路径 }, }, // 'style-loader', 使用MiniCssExtractPlugin时就不能使用style-loader了 { loader: 'css-loader', options: { importLoaders: 2 //该方式可以让@import引入的css文件再次执行一边css打包loader } }, ] } ] }, plugins: [ new MiniCssExtractPlugin({ // Options similar to the same options in webpackOptions.output // both options are optional filename: 'css/[name]_[hash].css', chunkFilename: 'css/[name]_[hash].chunk.css', }), ]}, base)

由于我们在dev和prod环境的css-loader不一样,所以我们将base的css-loader配置删除,移到dev下

// webpack.dev.jsconst base = require;const merge = require;const webpack = require;module.exports = merge({ mode: 'development', module: { rules: [ { test: /.css$/, use: [ // loader解析的顺序是从下到上,从右到左的顺序 'style-loader', //使用MiniCssExtractPlugin时就不能使用style-loader了 { loader: 'css-loader', options: { importLoaders: 2 //该方式可以让@import引入的css文件再次执行一边css打包loader } }, ] } ] }, output: {filename: '[name].js',chunkFilename: '[name].js',}}, base)

ok,此时我们就完成一个基本的打包es6,css的模块打包工具,为了支持更高的es6+语法,我们配置.babelrc文件,以及安装相应的npm包:

npm install @babel/polyfill core-js -D

.babelrc文件如下:

{ "presets": [ [ "@babel/preset-env", // 将ES6语法转换为es5 { "useBuiltIns": "usage", // 只编译需要编译的代码 "corejs": "3.0.1", } ], ]}

我们会看到babelrc文件里面有"useBuiltIns": "usage", 这个配置涉及到一个关于webpack打包的高级用法,tree-shaking。

tree-shaking:用于描述移除 JavaScript 上下文中的未引用代码。它依赖于 ES2015 模块语法的静态结构特性,例如 importexport 。这个术语和概念实际上是由 ES2015 模块打包工具 rollup 普及起来的。

我们通过使用tree-shaking,可以极大的减少代码的体积,对于提高打包性能很有帮助。为了使tree-shaking能生效,我们还要在webpack配置文件中开启:

// webpack.base.jsoptimization: { usedExports: true }

还有一个问题,由于tree-shaking是基于import 和export的,当我们用import引入css文件时,是没有导出的,所以我们需要配置忽略css文件的tree-shaking,在package.json中添加如下配置:

// package.json"sideEffects": [ "*.css", "*.less" ],

在打包的过程中,每次执行打包都会新建一个打包文件,我们想要每次打包之前都清除上一次打包的文件怎么办呢?我们可以使用clean-webpack-plugin来实现,首先先安装,然后具体配置如下:

// webpack.prod.jsplugins: [ new CleanWebpackPlugin() ],

该插件会默认清除dist目录下的打包文件。 接下来我们安装jquery:

npm install jquery -S

在index.js引入并使用:

import $ from 'jquery';import './styles/app.css'console.log.html;

本文由金沙官网线上发布于Web前端,转载请注明出处:金沙官网线上webpack4 系列教程(四): 单页面解决方

您可能还会对下面的文章感兴趣: