10分钟学会前端工程化(webpack5.0)

2022-10-04 21:55:06 浏览数 (1)

一、概要

1.1、前端工程化

随着前端的不断发展与壮大,前端变得越来越复杂,组件化、模块化、工程化、自动化成了前端发展中不可或缺的一部分,具体到前端工程化,面临的问题是如何提高编码->测试->维护阶段的生产效率。

前端工程化是使用软件工程的技术和方法来进行前端项目的开发、维护和管理。

前端工程化是依据业务特点,将前端开发的规范、流程、技术、工具、经验等形成规范并建立成一种标准的体系。

现在的项目可能会不停的迭代,发布就成了日常开发的一部分,前端不仅要保证功能还要保证性能,传统的一次次的发布效率会非常低,前端工程化一般都有会借助一些工具。

实现前端工程化的目的简单来说就是通过流程规范、自动化工具来提升前端的开发效率、性能、质量、多人协作能力以及开发体验,建立前端工程化是各个团队必经的成长过程。

1.1.1、前端工程化的任务

前端不仅要保证功能还要考虑性能,要减少http请求数量、压缩、合并、预处理、规范代码、清理、打包、转换等工作。

前端大部分情况下源代码无法直接运行,必须通过转换后才可以正常运行。构建就是做这件事情,把源代码转换成发布到线上的可执行 JavaScrip、CSS、HTML 代码,包括如下内容。

(1)、代码转换:TypeScript 编译成 JavaScript、SCSS 编译成 CSS 等。

(2)、文件优化:压缩 JavaScript、CSS、HTML 代码,压缩合并图片等。

(3)、代码分割:提取多个页面的公共代码、提取首屏不需要执行部分的代码让其异步加载。

(4)、模块合并:在采用模块化的项目里会有很多个模块和文件,需要构建功能把模块分类合并成一个文件。

(5)、自动刷新:监听本地源代码的变化,自动重新构建、刷新浏览器。

(6)、代码校验:在代码被提交到仓库前需要校验代码是否符合规范,以及单元测试是否通过。

(7)、自动发布:更新完代码后,自动构建出线上发布代码并传输给发布系统。

构建其实是工程化、自动化思想在前端开发中的体现,把一系列流程用代码去实现,让代码自动化地执行这一系列复杂的流程。 构建给前端开发注入了更大的活力,解放了我们的生产力。

1.2、前端工程化工具

历史上先后出现一系列构建工具,它们各有其优缺点。由于前端工程师很熟悉 JavaScript ,Node.js 又可以胜任所有构建需求,所以大多数构建工具都是用 Node.js 开发的。

构建工具的主要功能就是实现自动化处理,例如对代码进行检查、预编译、合并、压缩;生成雪碧图、sourceMap、版本管理;运行单元测试、监控等,当然有的工具还提供模块化、组件化的开发流程功能。

如果把工具按类型分可以分为这三类:

(一)、基于任务运行的工具:Grunt、Gulp

它们会自动执行指定的任务,就像流水线,把资源放上去然后通过不同插件进行加工,它们包含活跃的社区,丰富的插件,能方便的打造各种工作流。

(二)、基于模块化打包的工具:Browserify、Webpack、rollup.js

有过 Node.js 开发经历的应该对模块很熟悉,需要引用组件直接一个 require 就 OK,这类工具就是这个模式,还可以实现按需加载、异步加载模块。

(三)、整合型工具:Yeoman、FIS、jdf、Athena、cooking、weflow

使用了多种技术栈实现的脚手架工具,好处是即开即用,缺点就是它们约束了技术选型,并且学习成本相对较高。

1.2.1、Grunt

Grunt([ɡrʌnt]作呼噜声) 生态系统非常庞大,并且一直在增长。由于拥有数量庞大的插件可供选择,因此,你可以利用 Grunt 自动完成许多事,并且花费很少的代价。如果找不到你所需要的插件,那就自己动手创造一个 Grunt 插件,然后将其发布到 npm 上吧。

官网:https://gruntjs.com/

GitHub:https://github.com/gruntjs/

中文网:https://www.gruntjs.net/

对于需要反复重复的任务,例如压缩(minification)、编译、单元测试、linting等,自动化工具可以减轻你的劳动,简化你的工作。当你在 Gruntfile 文件正确配置好了任务,任务运行器就会自动帮你或你的小组完成大部分无聊的工作。

Grunt 是老牌的构建工具,特点是配置驱动,你需要做的就是了解各种插件的功能,然后把配置整合到 Gruntfile.js 中,下面是配置例子:

代码语言:javascript复制
module.exports = function(grunt) {
    grunt.initConfig({
        jshint: {
            files: ['Gruntfile.js', 'src/**/*.js', 'test/**/*.js'],
            options: {
                globals: {
                    jQuery: true
                }
            }
        },
        watch: {
            files: ['<%= jshint.files %>'],
            tasks: ['jshint']
        }
    });

    grunt.loadNpmTasks('grunt-contrib-jshint');
    grunt.loadNpmTasks('grunt-contrib-watch');

    grunt.registerTask('default', ['jshint']);
};

Grunt 缺点也是配置驱动,当任务非常多的情况下,试图用配置完成所有事简直就是个灾难;再就是它的 I/O 操作也是个弊病,它的每一次任务都需要从磁盘中读取文件,处理完后再写入到磁盘,例如:我想对多个 less 进行预编译、压缩操作,那么 Grunt 的操作就是:

代码语言:javascript复制
读取 less 文件 -> 编译成 css -> 存储到磁盘 -> 读取 css -> 压缩处理 -> 存储到磁盘

这样一来当资源文件较多,任务较复杂的时候性能就是个问题了。

1.2.2、Gulp

Gulp(ɡʌlp狼吞虎咽地吃,吞咽)是一个基于流的自动化构建工具。 除了可以管理和执行任务,还支持监听文件、读写文件。

中文网:https://www.gulpjs.com.cn/

官网:https://gulpjs.com/

特点:

易于使用:通过代码优于配置的策略,Gulp 让简单的任务简单,复杂的任务可管理。

构建快速:利用 Node.js 流的威力,你可以快速构建项目并减少频繁的 IO 操作。

插件高质:Gulp 严格的插件指南确保插件如你期望的那样简洁高质得工作。

易于学习:通过最少的 API,掌握 Gulp 不太费力,构建工作尽在掌握:如同一系列流管道。

Gulp 被设计得非常简单,只通过下面5种个方法就可以胜任几乎所有构建场景:

通过 gulp.task 注册一个任务;

通过 gulp.run 执行任务;

通过 gulp.watch 监听文件变化;

通过 gulp.src 读取文件;

通过 gulp.dest 写文件。

Gulp 的最大特点是引入了流的概念,同时提供了一系列常用的插件去处理流,流可以在插件之间传递

Gulp 特点是代码驱动,写任务就和写普通的 Node.js 代码一样:

代码语言:javascript复制
var gulp = require('gulp');
var pug = require('gulp-pug');
var less = require('gulp-less');
var minifyCSS = require('gulp-csso');

gulp.task('html', function(){
    return gulp.src('client/templates/*.pug')
        .pipe(pug())
        .pipe(gulp.dest('build/html'))
});

gulp.task('css', function(){
    return gulp.src('client/templates/*.less')
        .pipe(less())
        .pipe(minifyCSS())
        .pipe(gulp.dest('build/css'))
});

gulp.task('default', [ 'html', 'css' ]);

再一个对文件读取是流式操作(Stream),也就是说一次 I/O 可以处理多个任务,还是 less 的例子,Gulp 的流程就是:

代码语言:javascript复制
读取 less 文件 -> 编译成 css -> 压缩处理 -> 存储到磁盘

Gulp 作为任务类型的工具没有明显的缺点,唯一的问题可能就是完成相同的任务它需要写的代码更多一些,所以除非是项目有历史包袱(原有项目就是基于 Grunt 构建)在 Grunt 与 Gulp 对比看来还是比较推荐 Gulp!

适用场景:

通过上面的介绍可以看出它们侧重对整个过程的控制管理,实现简单、对架构无要求、不改变开发模式,所以非常适合前端、小型、需要快速启动的项目。

1.2.3、Yeoman

Yeoman([ˈjoʊmən]自耕农,自由民; 义勇骑兵队成员,young man)的目的不仅是要为新项目建立工作流,同时还是为了解决前端开发所面临的诸多严重问题,例如零散的依赖关系。

Yeoman是Google的团队和外部贡献者团队合作开发的,他的目标是通过Grunt(一个用于开发任务自动化的命令行工具)和Bower(一个HTML、CSS、Javascript和图片等前端资源的包管理器)的包装为开发者创建一个易用的工作流。

Yeoman是一个强健的脚手架与构建工具,库,及工作流程的组合,帮你网页开发者快速创建出漂亮而且引人入胜的网页程序,Yeoman帮助我们创建项目,提供更好的工具来使我们的项目更多样化。

Yeoman提供generator系统,一个generator是一个插件,在我们在一个完整的项目上使用‘yo’命令时,会运行该generator。通过这些官方的Generators,推出了Yeoman工作流,工作流是一个健壮、有自己特色的客户端堆栈,包含能快速构建漂亮的网络应用的工具和框架。Yeoman提供了负责开始项目开发的一切,没有任何让人头痛的手动配置。

Yeoman主要提供了三个工具:脚手架(yo),构建工具(grunt),包管理器(bower)。这三个工具是分别独立开发的,但是需要配合使用,来实现我们更高效的工作流模式。

小结:

在 Npm Script 和 Grunt 时代,Web 开发要做的事情变多,流程复杂,自动化思想被引入,用于简化流程;

在 Gulp 时代开始出现一些新语言用于提高开发效率,流式处理思想的出现是为了简化文件转换的流程,例如将 ES6 转换成 ES5。

在 Webpack 时代由于单页应用的流行,一个网页的功能和实现代码变得庞大,Web 开发向模块化改进。

这些构建工具都有各自的定位和专注点,它们之间既可以单独地完成任务,也可以相互搭配起来弥补各自的不足。 在了解这些常见的构建工具后,你需要根据自己的需求去判断应该如何选择和搭配它们才能更好地完成自己的需求。

经过多年的发展, Webpack 已经成为构建工具中的首选,原因是:

大多数团队在开发新项目时会采用紧跟时代的技术,这些技术几乎都会采用“模块化 新语言 新框架”,Webpack 可以为这些新项目提供一站式的解决方案;

Webpack 有良好的生态链和维护团队,能提供良好的开发体验和保证质量;

Webpack 被全世界的大量 Web 开发者使用和验证,能找到各个层面所需的教程和经验分享。

1.2.4、vite

Vite(法语意为 "快速的",发音 /vit/,发音同 "veet")是一种新型前端构建工具,能够显著提升前端开发体验。它主要由两部分组成:

  • 一个开发服务器,它基于 原生 ES 模块 提供了 丰富的内建功能,如速度快到惊人的 模块热更新(HMR)。
  • 一套构建指令,它使用 Rollup 打包你的代码,并且它是预配置的,可输出用于生产环境的高度优化过的静态资源。

Vite 意在提供开箱即用的配置,同时它的 插件 API 和 JavaScript API 带来了高度的可扩展性,并有完整的类型支持。

你可以在 为什么选 Vite 中了解更多关于项目的设计初衷。

vite中文网:https://vitejs.cn/

简单对 Webpack 和 Vite 进行一个对比:

Webpack

  • 支持的模块规范:ES Modules,CommonJS 和 AMD Modules;
  • Dev Server:通过 webpack-dev-server 托管打包好的模块;
  • 生产环境构建:webpack

Vite

  • 支持的模块规范:ES Modules;
  • Dev Server:原生 ES Modules;
  • 生产环境构建:Rollup

小结

由于浏览器原生 ES Modules 的支持,当浏览器发出请求时,Vite 可以在不将源码打包为一个 Bundle 文件的情况下,将源码文件转化为 ES Modules 文件之后返回给浏览器。这样 Vite 的应用启动和热更新 HMR 时的速度都不会随着应用规模的增加而变慢。

1.3、WebPack

webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。

Webpack 是一个打包模块化 JavaScript 的工具,在 Webpack 里一切文件皆模块,通过 Loader 转换文件,通过 Plugin 注入钩子,最后输出由多个模块组合成的文件。Webpack 专注于构建模块化项目。

其官网的首页图很形象的画出了 Webpack 是什么,如下:

一切文件:JavaScript、CSS、SCSS、图片、模板,在 Webpack 眼中都是一个个模块,这样的好处是能清晰的描述出各个模块之间的依赖关系,以方便 Webpack 对模块进行组合和打包。 经过 Webpack 的处理,最终会输出浏览器能使用的静态资源。

Webpack 具有很大的灵活性,能配置如何处理文件,大致使用如下:

代码语言:javascript复制
module.exports = {
// 所有模块的入口,Webpack 从入口开始递归解析出所有依赖的模块
    entry: './app.js',
    output: {
// 把入口所依赖的所有模块打包成一个文件 bundle.js 输出 
        filename: 'bundle.js'
    }
}

1.3.1、Webpack的特点

把一切都视为模块:不管是 CSS、JS、Image 还是 HTML 都可以互相引用,通过定义 entry.js,对所有依赖的文件进行跟踪,将各个模块通过 loader 和 plugins 处理,然后打包在一起。

按需加载:打包过程中 Webpack 通过 Code Splitting 功能将文件分为多个 chunks,还可以将重复的部分单独提取出来作为 commonChunk,从而实现按需加载。

优点:

专注于处理模块化的项目,能做到开箱即用一步到位;

通过 Plugin 扩展,完整好用又不失灵活;

使用场景不仅限于 Web 开发;

社区庞大活跃,经常引入紧跟时代发展的新特性,能为大多数场景找到已有的开源扩展;

良好的开发体验。

缺点:

Webpack的缺点是只能用于采用模块化开发的项目。

上手比较难、对于新手而言需要经历踩坑的过程。

对于Server 端渲染的多页应用有点力不从心。

小结:

Webpack 特别适合配合 React.js、Vue.js 构建单页面应用以及需要多人合作的大型项目,在规范流程都已约定好的情况下往往能极大的提升开发效率与开发体验。

1.3.2、资源

官网:https://webpack.js.org/

github:https://github.com/webpack/webpack

中文网:https://www.webpackjs.com/

深入浅出webpack电子书:http://webpack.wuhaolin.cn/

中文文档网:https://webpack.docschina.org/

1.3.3、工作流程

Webpack 是通过配置来实现管理,与 Grunt 不同的是它包含的许多自动化的黑盒操作所以配置起来会简单很多(但遇到问题调试起来就很麻烦),一个典型的配置如下:

代码语言:javascript复制
module.exports = {
    //插件项
    plugins: [commonsPlugin],
    //页面入口文件配置
    entry: {
        index : './src/js/page/index.js'
    },
    //入口文件输出配置
    output: {
        path: 'dist/js/page',
        filename: '[name].js'
    },
    module: {
        //加载器配置
        loaders: [
            { test: /.css$/, loader: 'style-loader!css-loader' },
            { test: /.js$/, loader: 'jsx-loader?harmony' },
            { test: /.scss$/, loader: 'style!css!sass?sourceMap'},
            { test: /.(png|jpg)$/, loader: 'url-loader?limit=8192'}
        ]
    },
    //其它解决方案配置
    resolve: {
        root: '/Users/Bell/github/flux-example/src', //绝对路径
        extensions: ['', '.js', '.json', '.scss'],
        alias: {
            AppStore : 'js/stores/AppStores.js',
            ActionType : 'js/actions/ActionType.js',
            AppAction : 'js/actions/AppAction.js'
        }
    }
};

1.3.4、搭建WebPack开发环境

(1)、安装NodeJS

在用 Webpack 执行构建任务时需要通过 webpack 可执行文件去启动构建任务,所以需要安装 webpack 可执行文件。 在安装 Webpack 前请确保你的系统安装了5.0.0及以上版本的 Node.js。

去https://nodejs.org/下载安装

设置国内npm的镜像

代码语言:javascript复制
$ npm install -g cnpm --registry=https://registry.npm.taobao.org

使用时用cnpm代替npm

(2)、安装webpack

安装 Webpack 到全局

安装到全局后你可以在任何地方共用一个 Webpack 可执行文件,而不用各个项目重复安装,安装方式如下:

代码语言:javascript复制
npm i -g webpack

cli:

所有cli参数:

代码语言:javascript复制
webpack-cli 3.1.2

Usage: webpack-cli [options]
       webpack-cli [options] --entry <entry> --output <output>
       webpack-cli [options] <entries...> --output <output>
       webpack-cli <command> [options]

For more information, see https://webpack.js.org/api/cli/.

Config options:
  --config               Path to the config file
                         [string] [default: webpack.config.js or webpackfile.js]
  --config-register, -r  Preload one or more modules before loading the webpack
                         configuration      [array] [default: module id or path]
  --config-name          Name of the config to use                      [string]
  --env                  Environment passed to the config, when it is a function
  --mode                 Enable production optimizations or development hints.
                                  [choices: "development", "production", "none"]

Basic options:
  --context    The base directory (absolute path!) for resolving the `entry`
               option. If `output.pathinfo` is set, the included pathinfo is
               shortened to this directory.
                                       [string] [default: The current directory]
  --entry      The entry point(s) of the compilation.                   [string]
  --watch, -w  Enter watch mode, which rebuilds on file change.        [boolean]
  --debug      Switch loaders to debug mode                            [boolean]
  --devtool    A developer tool to enhance debugging.                   [string]
  -d           shortcut for --debug --devtool eval-cheap-module-source-map
               --output-pathinfo                                       [boolean]
  -p           shortcut for --optimize-minimize --define
               process.env.NODE_ENV="production"                       [boolean]
  --progress   Print compilation progress in percentage                [boolean]

Module options:
  --module-bind       Bind an extension to a loader                     [string]
  --module-bind-post  Bind an extension to a post loader                [string]
  --module-bind-pre   Bind an extension to a pre loader                 [string]

Output options:
  --output, -o                  The output path and file for compilation assets
  --output-path                 The output directory as **absolute path**
                                (required).
                                       [string] [default: The current directory]
  --output-filename             Specifies the name of each output file on disk.
                                You must **not** specify an absolute path here!
                                The `output.path` option determines the location
                                on disk the files are written to, filename is
                                used solely for naming the individual files.
                                                   [string] [default: [name].js]
  --output-chunk-filename       The filename of non-entry chunks as relative
                                path inside the `output.path` directory.
       [string] [default: filename with [id] instead of [name] or [id] prefixed]
  --output-source-map-filename  The filename of the SourceMaps for the
                                JavaScript files. They are inside the
                                `output.path` directory.                [string]
  --output-public-path          The `publicPath` specifies the public URL
                                address of the output files when referenced in a
                                browser.                                [string]
  --output-jsonp-function       The JSONP function used by webpack for async
                                loading of chunks.                      [string]
  --output-pathinfo             Include comments with information about the
                                modules.                               [boolean]
  --output-library              Expose the exports of the entry point as library
                                                                        [string]
  --output-library-target       Type of library
         [string] [choices: "var", "assign", "this", "window", "self", "global",
      "commonjs", "commonjs2", "commonjs-module", "amd", "umd", "umd2", "jsonp"]

Advanced options:
  --records-input-path       Store compiler state to a json file.       [string]
  --records-output-path      Load compiler state from a json file.      [string]
  --records-path             Store/Load compiler state from/to a json file. This
                             will result in persistent ids of modules and
                             chunks. An absolute path is expected. `recordsPath`
                             is used for `recordsInputPath` and
                             `recordsOutputPath` if they left undefined.[string]
  --define                   Define any free var in the bundle          [string]
  --target                   Environment to build for                   [string]
  --cache                    Cache generated modules and chunks to improve
                             performance for multiple incremental builds.
                      [boolean] [default: It's enabled by default when watching]
  --watch-stdin, --stdin     Stop watching when stdin stream has ended [boolean]
  --watch-aggregate-timeout  Delay the rebuilt after the first change. Value is
                             a time in ms.                              [number]
  --watch-poll               Enable polling mode for watching           [string]
  --hot                      Enables Hot Module Replacement            [boolean]
  --prefetch                 Prefetch this request (Example: --prefetch
                             ./file.js)                                 [string]
  --provide                  Provide these modules as free vars in all modules
                             (Example: --provide jQuery=jquery)         [string]
  --labeled-modules          Enables labeled modules                   [boolean]
  --plugin                   Load this plugin                           [string]
  --bail                     Report the first error as a hard error instead of
                             tolerating it.            [boolean] [default: null]
  --profile                  Capture timing information for each module.
                                                       [boolean] [default: null]

Resolving options:
  --resolve-alias         Redirect module requests                      [string]
  --resolve-extensions    Redirect module requests                       [array]
  --resolve-loader-alias  Setup a loader alias for resolving            [string]

Optimizing options:
  --optimize-max-chunks      Try to keep the chunk count below a limit
  --optimize-min-chunk-size  Minimal size for the created chunk
  --optimize-minimize        Enable minimizing the output. Uses
                             optimization.minimizer.                   [boolean]

Stats options:
  --color, --colors               Enables/Disables colors on the console
                                           [boolean] [default: (supports-color)]
  --sort-modules-by               Sorts the modules list by property in module
                                                                        [string]
  --sort-chunks-by                Sorts the chunks list by property in chunk
                                                                        [string]
  --sort-assets-by                Sorts the assets list by property in asset
                                                                        [string]
  --hide-modules                  Hides info about modules             [boolean]
  --display-exclude               Exclude modules in the output         [string]
  --display-modules               Display even excluded modules in the output
                                                                       [boolean]
  --display-max-modules           Sets the maximum number of visible modules in
                                  output                                [number]
  --display-chunks                Display chunks in the output         [boolean]
  --display-entrypoints           Display entry points in the output   [boolean]
  --display-origins               Display origins of chunks in the output
                                                                       [boolean]
  --display-cached                Display also cached modules in the output
                                                                       [boolean]
  --display-cached-assets         Display also cached assets in the output
                                                                       [boolean]
  --display-reasons               Display reasons about module inclusion in the
                                  output                               [boolean]
  --display-depth                 Display distance from entry point for each
                                  module                               [boolean]
  --display-used-exports          Display information about used exports in
                                  modules (Tree Shaking)               [boolean]
  --display-provided-exports      Display information about exports provided
                                  from modules                         [boolean]
  --display-optimization-bailout  Display information about why optimization
                                  bailed out for modules               [boolean]
  --display-error-details         Display details about errors         [boolean]
  --display                       Select display preset
              [string] [choices: "", "verbose", "detailed", "normal", "minimal",
                                                          "errors-only", "none"]
  --verbose                       Show more details                    [boolean]
  --info-verbosity                Controls the output of lifecycle messaging
                                  e.g. Started watching files...
                 [string] [choices: "none", "info", "verbose"] [default: "info"]
  --build-delimiter               Display custom text after build output[string]

Options:
  --help, -h     Show help                                             [boolean]
  --version, -v  Show version number                                   [boolean]
  --silent       Prevent output from being displayed in stdout         [boolean]
  --json, -j     Prints the result as JSON.                            [boolean]

要安装 Webpack 到本项目,可按照你的需要选择以下任意命令运行:

代码语言:javascript复制
# npm i -D 是 npm install --save-dev 的简写,是指安装模块并保存到 package.json 的 devDependencies
# 安装最新稳定版
npm i -D webpack

# 安装指定版本
npm i -D webpack@<version>

# 安装最新体验版本
npm i -D webpack@beta

二、快速上手

2.1、第一个WebPack项目(不使用配置文件)

(1)、创建一个空项目,使用npm init -y初始化项目

(2)、在项目下创建src目录,在src目录中创建main.js,在src目录下创建js文件夹,在js文件夹中创建sum.js,sub.js文件,结构如下

 sum.js

代码语言:javascript复制
export default (a,b)=>a b;

sub.js

代码语言:javascript复制
export default (x,y)=>x-y;

main.js

代码语言:javascript复制
import sum from './js/sum'
import sub from './js/sub'

document.body.innerHTML=sum(100,200) "," sub(200,300);

(3)、添加依赖

代码语言:javascript复制
npm i -D webpack webpack-cli

(4)、打包

代码语言:javascript复制
npx webpack ./src/main.js --mode=development

开发模式生成的main.js文件:

生产模式生成的main.js文件:

 (5)、运行

在public目录下创建index.html,文件内容如下:

代码语言:javascript复制
<!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>webpack demo</title>
  </head>
  <body>
    <script src="../dist/main.js"></script>
  </body>
</html>

运行结果:

使用生产模式打包

代码语言:javascript复制
npx webpack ./src/main.js --mode=production

打包的代码变得异常简洁:

2.2、Hello World!(使用配置文件)

2.2.1、创建一个项目或目录

创建一个空项目或一个空目录,不一定需要使用IDE,这里我使用WebStorm

2.2.2、初始化项目

npm init -y (-y直接跳过提问阶段)

代码语言:javascript复制
name - 包名.
version - 包的版本号。
description - 包的描述。
homepage - 包的官网URL。
author - 包的作者,它的值是你在https://npmjs.org网站的有效账户名,遵循“账户名<邮件>”的规则,例如:zhangsan <zhangsan@163.com>。
contributors - 包的其他贡献者。
dependencies / devDependencies - 生产/开发环境依赖包列表。它们将会被安装在 node_module 目录下。
repository - 包代码的Repo信息,包括type和URL,type可以是git或svn,URL则是包的Repo地址。
main - main 字段指定了程序的主入口文件,require('moduleName') 就会加载这个文件。这个字段的默认值是模块根目录下面的 index.js。
keywords - 关键字

package.json详解

2.2.3、安装webpack

webpack4需要安装webpack-cli:

代码语言:javascript复制
npm i webpack webpack-cli --save-dev

2.2.4、创建目录与文件

src/bar.js

代码语言:javascript复制
//定义模块
//部分依赖lodash中的join方法
import {join} from 'lodash'

//导出一个默认模块
export default function bar() {
    function component() {
        //创建DOM元素
        var element=document.createElement("h2");
        //使用join连接数组将结果写入元素的html中
        element.innerHTML=join(['Hello','Webpack','!'],' ');
        return element;
    }
    //在body中添加子元素
    document.body.appendChild(component());
}

实现使用lodash的join 连接字符串,在网页中输出字符串

依赖lodash、npm安装lodash,在bar.js中import lodash的join方法,输出text

代码语言:javascript复制
npm i lodash --save

src/index.js

代码语言:javascript复制
//入口文件

//导入自定义好的模块
import bar from './bar';
//调用
bar();

创建配置文件webpack.config.js

代码语言:javascript复制
//webpack配置文件

//依赖node中的path模块
const path=require('path');

//定义一个默认模块对象
module.exports={
    //指定入口文件的位置
    entry:"./src/index.js",
    //设置输出结果
    output: {
        //路径,将相对路径转绝对路径
        path:path.resolve(__dirname,'dist'),
        //文件
        filename: "bundle.js"
    }
};

path.resolve获取文件的路径,_dirname为当前模块的绝对路径,详细解释如下:

代码语言:javascript复制
path.resolve()
//作用:path.resolve() 该方法将一些的 路径/路径段 解析为绝对路径。
//语法:path.resolve( [from…],to )
//说明:将参数to位置的字符解析到一个绝对路径里,[from … ]为选填项,路径源;

用法:
var path = require("path")     //引入node的path模块

path.resolve('/foo/bar', './baz')   // returns '/foo/bar/baz'
path.resolve('/foo/bar', 'baz')   // returns '/foo/bar/baz'
path.resolve('/foo/bar', '/baz')   // returns '/baz'
path.resolve('/foo/bar', '../baz')   // returns '/foo/baz'
path.resolve('home','/foo/bar', '../baz')   // returns '/foo/baz'
path.resolve('home','./foo/bar', '../baz')   // returns '/home/foo/baz'
path.resolve('home','foo/bar', '../baz')   // returns '/home/foo/baz'
//总结:从后向前,若字符以 / 开头,不会拼接到前面的路径;若以 ../ 开头,拼接前面的路径,且不含最后一节路径;若以 ./ 开头 或者没有符号 则拼接前面路径;

//另:path.resolve总是返回一个以相对于当前的工作目录(working directory)的绝对路径。

index.html

webpack创建dist文件在dist中生成打包文件,然后再index.html中引用index.js文件

代码语言:javascript复制
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Hello WebPack</title>
</head>
<body>
<h2>Hello WebPack</h2>
<script src="dist/bundle.js"></script>
</body>
</html>

2.2.5、使用webpack命令打包

可以指定配置文件

代码语言:javascript复制
webpack --config webpack.config.js

也可以使用默认的配置文件

代码语言:javascript复制
webpack

打包的结果:

代码语言:javascript复制
!function(n){var t={};function r(e){if(t[e])return t[e].exports;var u=t[e]={i:e,l:!1,exports:{}};return n[e].call(u.exports,u,u.exports,r),u.l=!0,u.exports}r.m=n,r.c=t,r.d=function(n,t,e){r.o(n,t)||Object.defineProperty(n,t,{enumerable:!0,get:e})},r.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},r.t=function(n,t){if(1&t&&(n=r(n)),8&t)return n;if(4&t&&"object"==typeof n&&n&&n.__esModule)return n;var e=Object.create(null);if(r.r(e),Object.defineProperty(e,"default",{enumerable:!0,value:n}),2&t&&"string"!=typeof n)for(var u in n)r.d(e,u,function(t){return n[t]}.bind(null,u));return e},r.n=function(n){var t=n&&n.__esModule?function(){return n.default}:function(){return n};return r.d(t,"a",t),t},r.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},r.p="",r(r.s=3)}([function(n,t,r){(function(n,e){var u;

 bundle.js文件:

2.2.6、运行

三、核心概念与执行过程

本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。

从 webpack v4.0.0 开始,可以不用引入一个配置文件。然而,webpack 仍然还是高度可配置的。在开始前你需要先理解几个核心概念:

3.1、模块(Module)

在Webpack里一切皆模块,一个模块对应着一个文件。Webpack 会从配置的Entry开始递归找出所有依赖的模块。

webpack把一切都视为模块,不管是 CSS、JS、Image 还是 HTML 都可以互相引用。Node.js 从最一开始就支持模块化编程。然而,在 web,模块化的支持正缓慢到来。在 web 存在多种支持 JavaScript 模块化的工具,这些工具各有优势和限制。webpack 基于从这些系统获得的经验教训,并将模块的概念应用于项目中的任何文件。

什么是 webpack 模块

对比 Node.js 模块,webpack 模块能够以各种方式表达它们的依赖关系,几个例子如下:

  1. ES2015 import 语句
  2. CommonJS require() 语句
  3. AMD define 和 require 语句
  4. css/sass/less 文件中的 @import 语句。
  5. 样式(url(...))或 HTML 文件(<img src=...>)中的图片链接(image url)

支持的模块类型

webpack 通过 loader 可以支持各种语言和预处理器编写模块。loader 描述了 webpack 如何处理 非 JavaScript(non-JavaScript) _模块_,并且在 bundle 中引入这些依赖。 webpack 社区已经为各种流行语言和语言处理器构建了 loader,包括:

  1. CoffeeScript
  2. TypeScript
  3. ESNext (Babel)
  4. Sass
  5. Less
  6. Stylus

3.2、入口(entry)

入口,Webpack 执行构建的第一步将从 Entry 开始,可抽象成输入。

入口起点(entry point)指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。

每个依赖项随即被处理,最后输出到称之为 bundles 的文件中,我们将在下一章节详细讨论这个过程。

可以通过在 webpack 配置中配置 entry 属性,来指定一个入口起点(或多个入口起点)。默认值为 ./src。

接下来我们看一个 entry 配置的最简单例子:

代码语言:javascript复制
webpack.config.js

module.exports = {
    entry: './path/to/my/entry/file.js'
};

根据应用程序的特定需求,可以以多种方式配置 entry 属性。

多入口与多出口:

代码语言:javascript复制
{
  entry: {
    app: './src/app.js',
    search: './src/search.js'
  },
  output: {
    filename: '[name].js',
    path: __dirname   '/dist'
  }
}

// 写入到硬盘:./dist/app.js, ./dist/search.js

常用的占位:

  1. [hash]:模块标识符(module identifier)的 hash
  2. [chunkhash]:chunk 内容的 hash
  3. [name]:模块名称,key的名称,非文件名称
  4. [id]:模块标识符(module identifier)
  5. [query]:模块的 query,例如,文件名 ? 后面的字符串

3.3、出口(output)

输出结果,在 Webpack 经过一系列处理并得出最终想要的代码后输出结果。

output 属性告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件,默认值为 ./dist。基本上,整个应用程序结构,都会被编译到你指定的输出路径的文件夹中。你可以通过在配置中指定一个 output 字段,来配置这些处理过程:

代码语言:javascript复制
webpack.config.js

const path = require('path');

module.exports = {
    entry: './path/to/my/entry/file.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'my-first-webpack.bundle.js'
    }
};

在上面的示例中,我们通过 output.filename 和 output.path 属性,来告诉 webpack bundle 的名称,以及我们想要 bundle 生成(emit)到哪里。可能你想要了解在代码最上面导入的 path 模块是什么,它是一个 Node.js 核心模块,用于操作文件路径。

示例:

3.4、模块转换器(loader)

模块转换器,用于把模块原内容按照需求转换成新内容。

loader 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后你就可以利用 webpack 的打包能力,对它们进行处理。

本质上,webpack loader 将所有类型的文件,转换为应用程序的依赖图(和最终的 bundle)可以直接引用的模块。

注意,loader 能够 import 导入任何类型的模块(例如 .css 文件),这是 webpack 特有的功能,其他打包程序或任务执行器的可能并不支持。我们认为这种语言扩展是有很必要的,因为这可以使开发人员创建出更准确的依赖关系图。

在更高层面,在 webpack 的配置中 loader 有两个目标:

  1. test 属性,用于标识出应该被对应的 loader 进行转换的某个或某些文件。
  2. use 属性,表示进行转换时,应该使用哪个 loader。
代码语言:javascript复制
webpack.config.js

const path = require('path');

const config = {
    output: {
        filename: 'my-first-webpack.bundle.js'
    },
    module: {
        rules: [
            { test: /.txt$/, use: 'raw-loader' }
        ]
    }
};

module.exports = config;

以上配置中,对一个单独的 module 对象定义了 rules 属性,里面包含两个必须属性:test 和 use。这告诉 webpack 编译器(compiler) 如下信息:

“webpack 编译器,当你碰到「在 require()/import 语句中被解析为 '.txt' 的路径」时,在你对它打包之前,先使用 raw-loader 转换一下。”

将ts转换成js示例:

(1)、添加依赖

npm i -D webpack webpack-cli typescript ts-loader

(2)、修改配置文件

代码语言:javascript复制
// 引入一个包
const path = require('path')


// webpack 中的所有的配置信息都应该写在 module.exports 中
module.exports = {

   // 指定入口文件
   entry: "./src/index.ts",

   // 指定打包文件所在目录
   output: {
       // 指定打包后的目录
       path: path.resolve(__dirname, 'dist'),

       // 打包后文件的文件名
       filename: "bundle.js"
   },


   // 指定 webpack 打包时要使用模块
   module: {

       // 指定要加载的规则
       rules: [{
           // test 指定规则生效的文件
           test: /.ts$/,
           // 要使用的 loader
           use: 'ts-loader',
           // 要排除的文件
           exclude: /node_modules/
       }]

   }
}

(3)、准备好ts文件

(4)、使用命令打包

npx webpack

(5)、查看结果

3.5、插件(plugins)

扩展插件,在 Webpack 构建流程中的特定时机注入扩展逻辑来改变构建结果或做你想要的事情。

loader 被用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。插件接口功能极其强大,可以用来处理各种各样的任务。

想要使用一个插件,你只需要 require() 它,然后把它添加到 plugins 数组中。多数插件可以通过选项(option)自定义。你也可以在一个配置文件中因为不同目的而多次使用同一个插件,这时需要通过使用 new 操作符来创建它的一个实例。

3.5.1、html-webpack-plugin

html-webpack-plugin的主要作用就是在webpack构建后生成html文件,同时把构建好入口js文件引入到生成的html文件中。

在src下创建一个模板index.html

代码语言:javascript复制
<!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><%=htmlWebpackPlugin.options.title %></title>
  </head>
  <body></body>
</html>

安装 webpack 插件, 使的打包后会自动创建 html 文件, 并自动引入相关的 js 文件 npm i -D html-webpack-plugin

修改配置文件 webpack.config.js

代码语言:javascript复制
const HTMLWebpackPlugin=require('html-webpack-plugin');

module.exports = {
    entry:"./src/main.ts",
    output:{
        path: __dirname   "/dist",
        filename:"[chunkhash].js"
    },
    mode:"development",
    module:{
        rules:[
            {
                test: /.ts$/,
                exclude: /node_modules/,
                use: ["ts-loader"]
            }
        ]
    },
    plugins:[
        new HTMLWebpackPlugin({
            template: "./src/index.html",
            title:"Hello Webpack",
            filename:"index.html"
        })
    ],
    resolve:{
        extensions: [".ts", ".js"]
    }
}

package.json

代码语言:javascript复制
{
  "name": "webpackdemo03",
  "version": "1.0.0",
  "description": "",
  "main": "./src/main.ts",
  "scripts": {
    "test": "echo "Error: no test specified" && exit 1",
    "build":"npx webpack"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "html-webpack-plugin": "^5.5.0",
    "ts-loader": "^9.4.1",
    "typescript": "^4.8.4",
    "webpack": "^5.74.0",
    "webpack-cli": "^4.10.0"
  }
}

打包

查看结果

3.5.2、clean-webpack-plugin

安装 webpack 插件, 在打包之前清除 dist 目录下的旧文件, 再生成新文件, 避免缓存

npm i -D clean-webpack-plugin

插件源码与使用方法:https://github.com/johnagan/clean-webpack-plugin#options-and-defaults-optional

修改配置文件

打包

查看效果

3.5.3、webpack-dev-server

安装 webpack 开发服务器插件 ( 可以根据项目的改变自动刷新 ) npm i -D webpack-dev-server 安装时要注意版本兼容问题, webpack 5 版本不兼容 比较高版本的 webpack-dev-server, 所以这里安装的是 npm i -D webpack-dev-server@3.11.2 配置 package.json 文件 "serve": "webpack serve --open",

修改webpack.config.js,添加服务器配置项

代码语言:javascript复制
      // 开发服务器配置
  // 只会在内存中打包  不会有任何输出
  // 启动devServer的命令为 npx webpack-dev-server(需要安装webpack-dev-server依赖)
  // 比如 端口号 压缩 代理等会在这里配置
  devServer: {
    compress:true, // 开启gzip压缩
    port:8008, // 启动的端口号
    open:true, // 启动服务后自动打开浏览器
    hot: true,  // 不配置此项也能热更新,在 webpack 5 中 HMR 已自动支持。
    // proxy:{} // 代理
  },

3.6、模式(mode)

通过选择 development 或 production 之中的一个,来设置 mode 参数,你可以启用相应模式下的 webpack 内置的优化

代码语言:javascript复制
module.exports = {
    mode: 'production'
};

webpack4允许我们指定编译使用开发模式还是生产模式,这由mode这个配置来控制,value为枚举值:development/production,分别对应开发模式和生产模式(这个配置可以作为命令行的配置参数也可以作为配置文件中的一个配置项,默认值是production,即生产模式)。

源码还是不支持调试(都用eval函数包住),指定编译时的source-map生成方式,默认值是eval,可以解决问题。

3.7、代码块(Chunk)

一个 Chunk 由多个模块组合而成,用于代码合并与分割。

3.8、WebPack执行过程

Webpack 启动后会从 Entry 里配置的 Module 开始递归解析 Entry 依赖的所有 Module。 每找到一个 Module, 就会根据配置的 Loader 去找出对应的转换规则,对 Module 进行转换后,再解析出当前 Module 依赖的 Module。 这些模块会以 Entry 为单位进行分组,一个 Entry 和其所有依赖的 Module 被分到一个组也就是一个 Chunk。最后 Webpack 会把所有 Chunk 转换成文件输出。 在整个流程中 Webpack 会在恰当的时机执行 Plugin 里定义的逻辑。

Webpack从入口(entry)开始工作,通常这些是JavaScript模块,其中webpack开始其遍历过程。在此过程中,webpack会根据加载器配置评估入口(entry)匹配,这些配置告诉webpack如何转换每个匹配。

入口(entry)本身就是一个模块。当webpack遇到一个入口时,webpack会尝试使用入口的resolve配置将入口与文件系统匹配。除了node_modules之外,我们还可以告诉webpack对特定目录执行查找。也可以调整webpack与文件扩展名匹配的方式,并且可以为目录定义特定的别名。该耗竭与包章涵盖了更详细的这些想法。

如果解析通过失败,webpack会引发运行时错误。如果webpack设法正确解析文件,webpack将根据加载器定义对匹配的文件执行处理。每个加载器对模块内容应用特定的转换。

可以通过多种方式配置加载程序与已解析文件匹配的方式,包括文件类型和文件系统中的位置。Webpack的灵活性甚至允许我们根据文件导入项目的位置对文件应用特定的转换。

对webpack的加载器执行相同的解析过程。Webpack允许我们在确定应使用哪个加载器时应用类似的逻辑。由于这个原因,装载程序已经解析了自己的配置。如果webpack无法执行加载程序查找,则会引发运行时错误。

在实际应用中你可能会遇到各种奇怪复杂的场景,不知道从哪开始。 根据以上总结,你会对 Webpack 有一个整体的认识,这能让你在以后使用 Webpack 的过程中快速知道应该通过配置什么去完成你想要的功能,而不是无从下手。

 下图可以简易的描述出webpack打包过程,该过程主要分为三个阶段:module构建、trunk构建和产出三个阶段:

四、模块转换器Loader

loader 用于对模块的源代码进行转换。loader 可以使你在 import 或"加载"模块时预处理文件。因此,loader 类似于其他构建工具中“任务(task)”,并提供了处理前端构建步骤的强大方法。loader 可以将文件从不同的语言(如 TypeScript)转换为 JavaScript,或将内联图像转换为 data URL。loader 甚至允许你直接在 JavaScript 模块中 import CSS文件!

loader 是对应用程序中资源文件进行转换。它们是(运行在 Node.js 中的)函数,可以将资源文件作为参数的来源,然后返回新的资源文件。

4.1、loader特性

loader 支持链式传递。能够对资源使用流水线(pipeline)。一组链式的 loader 将按照相反的顺序执行。loader 链中的第一个 loader 返回值给下一个 loader。在最后一个 loader,返回 webpack 所预期的 JavaScript。

  • loader 可以是同步的,也可以是异步的。
  • loader 运行在 Node.js 中,并且能够执行任何可能的操作。
  • loader 接收查询参数。用于对 loader 传递配置。
  • loader 也能够使用 options 对象进行配置。

除了使用 package.json 常见的 main 属性,还可以将普通的 npm 模块导出为 loader,做法是在 package.json 里定义一个 loader 字段。

插件(plugin)可以为 loader 带来更多特性。

loader 能够产生额外的任意文件。

loader 通过(loader)预处理函数,为 JavaScript 生态系统提供了更多能力。 用户现在可以更加灵活地引入细粒度逻辑,例如压缩、打包、语言翻译和其他更多。

loader 遵循标准的模块解析。多数情况下,loader 将从模块路径(通常将模块路径认为是 npm install, node_modules)解析。

loader 模块需要导出为一个函数,并且使用 Node.js 兼容的 JavaScript 编写。通常使用 npm 进行管理,但是也可以将自定义 loader 作为应用程序中的文件。按照约定,loader 通常被命名为 xxx-loader(例如 json-loader)。

4.2、使用loader的三种方式

(1)、配置(推荐):在 webpack.config.js 文件中指定 loader

module.rules 允许你在 webpack 配置中指定多个 loader。 这是展示 loader 的一种简明方式,并且有助于使代码变得简洁。同时让你对各个 loader 有个全局概览:

代码语言:javascript复制
  module: {
    rules: [
      {
        test: /.css$/,
        use: [
          { loader: 'style-loader' },
          {
            loader: 'css-loader',
            options: {
              modules: true
            }
          }
        ]
      }
    ]
  }

Loaders需要单独安装并且需要在webpack.config.js中的modules关键字下进行配置,Loaders的配置包括以下几方面:

代码语言:javascript复制
test:一个用以匹配loaders所处理文件的拓展名的正则表达式(必须)

loader:loader的名称(必须)

include/exclude:手动添加必须处理的文件(文件夹)或屏蔽不需要处理的文件(文件夹)(可选)

query:为loaders提供额外的设置选项(可选)

(2)、内联:在每个 import 语句中显式指定 loader

可以在 import 语句或任何等效于 "import" 的方式中指定 loader。使用 ! 将资源中的 loader 分开。分开的每个部分都相对于当前目录解析。

代码语言:javascript复制
import Styles from 'style-loader!css-loader?modules!./styles.css';

通过前置所有规则及使用 !,可以对应覆盖到配置中的任意 loader。

选项可以传递查询参数,例如 ?key=value&foo=bar,或者一个 JSON 对象,例如 ?{"key":"value","foo":"bar"}。

尽可能使用 module.rules,因为这样可以减少源码中的代码量,并且可以在出错时,更快地调试和定位 loader 中的问题。

(3)、CLI:在 shell 命令中指定它们

你也可以通过 CLI 使用 loader:

代码语言:javascript复制
webpack --module-bind jade-loader --module-bind 'css=style-loader!css-loader'

这会对 .jade 文件使用 jade-loader,对 .css 文件使用 style-loader 和 css-loader。

4.3、常见的loader

4.3.1、文件

  • raw-loader 加载文件原始内容(utf-8)
  • val-loader 将代码作为模块执行,并将 exports 转为 JS 代码
  • url-loader 像 file loader 一样工作,但如果文件小于限制,可以返回 data URL
  • file-loader 将文件发送到输出文件夹,并返回(相对)URL

4.3.2、JSON

  • json-loader 加载 JSON 文件(默认包含)
  • json5-loader 加载和转译 JSON 5 文件
  • cson-loader 加载和转译 CSON 文件

4.3.3、转换编译(Transpiling)

  • script-loader 在全局上下文中执行一次 JavaScript 文件(如在 script 标签),不需要解析
  • babel-loader 加载 ES2015 代码,然后使用 Babel 转译为 ES5
  • buble-loader 使用 Bublé 加载 ES2015 代码,并且将代码转译为 ES5
  • traceur-loader 加载 ES2015 代码,然后使用 Traceur 转译为 ES5
  • ts-loader 或 awesome-typescript-loader 像 JavaScript 一样加载 TypeScript 2.0
  • coffee-loader 像 JavaScript 一样加载 CoffeeScript

4.3.4、模板(Templating)

  • html-loader 导出 HTML 为字符串,需要引用静态资源
  • pug-loader 加载 Pug 模板并返回一个函数
  • jade-loader 加载 Jade 模板并返回一个函数
  • markdown-loader 将 Markdown 转译为 HTML
  • react-markdown-loader 使用 markdown-parse parser(解析器) 将 Markdown 编译为 React 组件
  • posthtml-loader 使用 PostHTML 加载并转换 HTML 文件
  • handlebars-loader 将 Handlebars 转移为 HTML
  • markup-inline-loader 将内联的 SVG/MathML 文件转换为 HTML。在应用于图标字体,或将 CSS 动画应用于 SVG 时非常有用。

4.3.5、样式

  • style-loader 将模块的导出作为样式添加到 DOM 中
  • css-loader 解析 CSS 文件后,使用 import 加载,并且返回 CSS 代码
  • less-loader 加载和转译 LESS 文件
  • sass-loader 加载和转译 SASS/SCSS 文件
  • postcss-loader 使用 PostCSS 加载和转译 CSS/SSS 文件
  • stylus-loader 加载和转译 Stylus 文件

4.3.6、清理和测试(Linting && Testing)

  • mocha-loader 使用 mocha 测试(浏览器/NodeJS)
  • eslint-loader PreLoader,使用 ESLint 清理代码
  • jshint-loader PreLoader,使用 JSHint 清理代码
  • jscs-loader PreLoader,使用 JSCS 检查代码样式
  • coverjs-loader PreLoader,使用 CoverJS 确定测试覆盖率

4.3.7、框架(Frameworks)

  • vue-loader 加载和转译 Vue 组件
  • polymer-loader 使用选择预处理器(preprocessor)处理,并且 require() 类似一等模块(first-class)的 Web 组件
  • angular2-template-loader 加载和转译 Angular 组件

4.4、raw-loader(文件原始内容转换器)

一个可以用于加载文件作为字符串使用的加载器,使用UTF-8编码。

安装

代码语言:javascript复制
npm i --D raw-loader

 安装结果:

用法一:

通过 webpack 配置、命令行或者内联使用 loader。

代码语言:javascript复制
webpack.config.js

module.exports = {
    module: {
        rules: [
            {
                test: /.txt$/,
                use: 'raw-loader'
            }
        ]
    }
}

在你的项目中

代码语言:javascript复制
import txt from './file.txt';

用法二:通过命令行(CLI)

代码语言:javascript复制
webpack --module-bind 'txt=raw-loader'

用法三:在你的项目中

代码语言:javascript复制
import txt from 'file.txt';

内联使用

代码语言:javascript复制
import txt from 'raw-loader!./file.txt';

示例:

webpack.config.json

代码语言:javascript复制
//webpack配置文件

//依赖node中的path模块
var path=require('path');

//定义一个默认模块对象
module.exports={
    //指定入口文件的位置,多入口
    entry:{
        index:"./src/index.js",
        main:"./src/main.js"
    },
    //设置输出结果
    output: {
        //路径,将相对路径转绝对路径
        path:path.resolve(__dirname,'dist'),
        //文件,[name]是模块名称,占位
        filename: "[name].bundle.js"
    },
    module: {  //模块处理
        rules: [ //处理器
            {
                test:/.txt$/,  //当模块的后缀为.txt时匹配
                use: "raw-loader"  //模块转换器,可以以对象的形式指定参数
            }
        ]
    },
    mode: "development"
};

src/file1.txt

代码语言:javascript复制
A loader for webpack that lets you import files as a string.

src/bar.js

代码语言:javascript复制
//定义模块
//部分依赖lodash中的join方法
import {join} from 'lodash';
//导入模块,获得file1.txt中的文件内容,被raw-loader处理
import message from './file1.txt';

//导出一个默认模块
export default function bar() {
    function component() {
        //创建DOM元素
        var element=document.createElement("h2");
        //使用join连接数组将结果写入元素的html中
        element.innerHTML=join(['Hello','Webpack','!'],' ') "<br/>" message;
        return element;
    }
    //在body中添加子元素
    document.body.appendChild(component());
}

运行结果:

内联使用模块处理器:

4.5、CSS Loader(样式处理)

webpack提供两个工具处理样式表,css-loader 和 style-loader,二者处理的任务不同,css-loader使你能够使用类似@import 和 url(...)的方法实现 require()的功能,style-loader将所有的计算后的样式加入页面中,二者组合在一起使你能够把样式表嵌入webpack打包后的JS文件中。

  • css-loader: 加载.css文件
  • style-loader:使用<style>将css-loader内部样式注入到我们的HTML页面

css-loader详解:

https://www.npmjs.com/package/css-loader

https://www.webpackjs.com/loaders/css-loader/

style-loader详解:

https://www.npmjs.com/package/style-loader

https://www.webpackjs.com/loaders/style-loader/

4.5.1、安装

代码语言:javascript复制
//安装
npm i style-loader css-loader -D

4.5.2、配置

代码语言:javascript复制
const path = require("path");

module.exports = {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: "bundle.js"
    },
    module: {
        rules: [
            {
                test:/.css/,
                use:['style-loader',{
                    loader: 'css-loader',
                    options: {
                        sourceMap:true
                    }
                }]
            }
        ]
    },
    mode: "development"
};

4.5.3、定义样式与引用

base.css

代码语言:javascript复制
h2{
    height: 40px;
    line-height: 40px;
    background: crimson;
    color:#fff;
}

bar.js

代码语言:javascript复制
import {join} from 'lodash';
import base from '../css/base.css';

export default function bar() {
    function component() {
        var element=document.createElement("h2");
        element.innerHTML=join(['Hello','Webpack!']);
        return element;
    }
    document.body.appendChild(component());
}

4.5.4、打包运行

打包:

运行:

生成代码:

4.5.5、注意事项

  1. rules里的数据类型为对象,每一个loader都是一个对象
  2. test表示loader要处理什么类型的文件,这里用了一个正则去匹配文件类型
  3. use表示要使用哪个loader,它的值是个数组,loader的使用顺序是从后往前
  4. 这个loader的意思为,在入口文件里找到.css类型的文件,先拿css-loader去处理成浏览器认识的css,再拿style-loader把处理后的css放在页面的style标签里

4.6、sass-loader(加载和转译 SASS/SCSS 文件)

加载sass或scss文件并转译成css

用css-loader或raw-loader 转换成一个JS模块或用ExtractTextPlugin插件将样式分隔成一个单独文件。

安装

代码语言:javascript复制
npm i sass-loader node-sass --D

node-sass 和 webpack 是 sass-loader 的 peerDependency,因此能够精确控制它们的版本。

示例

css/baseScss.scss

代码语言:javascript复制
$height:50px;
$color:#3366ff;

h2{
  background: $color;
  height: $height;
  line-height: $height;
  color: white;
  padding-left: $height/5;
}

通过将 style-loader 和 css-loader 与 sass-loader 链式调用,可以立刻将样式作用在 DOM 元素。

配置webpack.config.json

代码语言:javascript复制
//webpack配置文件

//依赖node中的path模块
var path=require('path');

//定义一个默认模块对象
module.exports={
    //指定入口文件的位置,多入口
    entry:{
        index:"./src/index.js",
        main:"./src/main.js"
    },
    //设置输出结果
    output: {
        //路径,将相对路径转绝对路径
        path:path.resolve(__dirname,'dist'),
        //文件,[name]是模块名称,占位
        filename: "[name].bundle.js"
    },
    module: {  //模块处理
        rules: [ //处理器
            {
                test:/.txt$/,  //当模块的后缀为.txt时匹配
                use: "raw-loader"  //模块转换器,可以以对象的形式指定参数
            },
            {
                test:/.css$/,  //匹配所有css模块
                //use表示要使用哪个loader,它的值是个数组,loader的使用顺序是从后往前
                use: ["style-loader",{
                    loader: "css-loader",  //转换器名称
                    options: {  //配置选项
                        modules:true,  //模块化
                        sourceMap:true  //是否生成调试文件
                    }
                }]  //使用多个模块转换器
            },
            {
                test: /.scss$/,
                use: [{
                    loader: "style-loader" // 将 JS 字符串生成为 style 节点
                }, {
                    loader: "css-loader" // 将 CSS 转化成 CommonJS 模块
                }, {
                    loader: "sass-loader" // 将 Scss 编译成 CSS
                }]
            }
        ]
    },
    mode: "development"
};

导入scss作为模块

代码语言:javascript复制
//定义模块
//部分依赖lodash中的join方法
import {join} from 'lodash';
//导入模块,获得file1.txt中的文件内容,被raw-loader处理
import message from './file1.txt';
//导入样式文件
//import '../css/baseCss.css'
//导入预处理样式文件
import '../css/baseScss.scss'

//导出一个默认模块
export default function bar() {
    function component() {
        //创建DOM元素
        var element=document.createElement("h2");
        //使用join连接数组将结果写入元素的html中
        element.innerHTML=join(['Hello','Webpack','!'],' ') "<br/>" message;
        return element;
    }
    //在body中添加子元素
    document.body.appendChild(component());
}

打包后运行结果:

通常,生产环境下比较推荐的做法是,使用 ExtractTextPlugin 将样式表抽离成专门的单独文件。这样,样式表将不再依赖于 JavaScript:

代码语言:javascript复制
const ExtractTextPlugin = require("extract-text-webpack-plugin");

const extractSass = new ExtractTextPlugin({
    filename: "[name].[contenthash].css",
    disable: process.env.NODE_ENV === "development"
});

module.exports = {
    ...
        module
:
{
    rules: [{
        test: /.scss$/,
        use: extractSass.extract({
            use: [{
                loader: "css-loader"
            }, {
                loader: "sass-loader"
            }],
// 在开发环境使用 style-loader
            fallback: "style-loader"
        })
    }]
}
,
plugins: [
    extractSass
]
}
;

4.7、url-loader(路径处理器)

 Webpack 允许你在js文件中require图片 , 通过 url-loader和file-loader来预处理图片文件,将图片转换成base64编码。

安装

代码语言:javascript复制
npm install --save-dev url-loader file-loader

用法

url-loader 功能类似于 file-loader,但是在文件大小(单位 byte)低于指定的限制时,可以返回一个 DataURL。

代码语言:javascript复制
import img from './image.png'

配置

代码语言:javascript复制
webpack.config.js

module.exports = {
    module: {
        rules: [
            {
                test: /.(png|jpg|gif)$/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 8192
                        }
                    }
                ]
            }
        ]
    }
}

示例

webpack.config.js

代码语言:javascript复制
//webpack配置文件

//依赖node中的path模块
var path=require('path');

//定义一个默认模块对象
module.exports={
    entry:{app01:"./src/app01.js"},
    //设置输出结果
    output: {
        //路径,将相对路径转绝对路径
        path:path.resolve(__dirname,'dist'),
        //文件,[name]是模块名称,占位
        filename: "[name].bundle.js"
    },
    module: {
        rules: [
            {
                //如是文件名是gif,jpg,jpeg,bmp图片则匹配,不分大小写
                test:/.(gif|jpe?g|png|bmp)/i,
                use: {
                    loader: "url-loader",
                    options: {
                        //如果图片的大小小于1024byte(B)时转换成dataUrl,base64编码
                        limit:1024,
                        fallback:"responsive-loader"
                    }
                }
            }
        ]
    }
};

src/app01.js

代码语言:javascript复制
//导入大图片 404byte
import srcBig from '../img/big.jpg';
//导入小图片 100Kbyte
import srcSma from '../img/small.gif';

console.log(srcBig);
console.log(srcSma)

alert("Hello App01!");

//创建大图片DOM
var img1=document.createElement("img");
//指定url
img1.src=srcBig;
//添加到文档中
document.body.appendChild(img1);

//创建小图片DOM
var img2=document.createElement("img");
//指定url
img2.src=srcSma;
//添加到文档中
document.body.appendChild(img2);

运行结果:

五、插件plugins

5.1、插件概要

Plugin 是用来扩展 Webpack 功能的,通过在构建流程里注入钩子实现,它给 Webpack 带来了很大的灵活性。

插件是 webpack 的支柱功能。webpack 自身也是构建于,你在 webpack 配置中用到的相同的插件系统之上!插件目的在于解决 loader 无法实现的其他事。

webpack 插件是一个具有 apply 属性的 JavaScript 对象。apply 属性会被 webpack compiler 调用,并且 compiler 对象可在整个编译生命周期访问。

代码语言:javascript复制
ConsoleLogOnBuildWebpackPlugin.js

const pluginName = 'ConsoleLogOnBuildWebpackPlugin';

class ConsoleLogOnBuildWebpackPlugin {
    apply(compiler) {
        compiler.hooks.run.tap(pluginName, compilation => {
            console.log("webpack 构建过程开始!");
        });
    }
}

compiler hook 的 tap 方法的第一个参数,应该是驼峰式命名的插件名称。建议为此使用一个常量,以便它可以在所有 hook 中复用。

由于插件可以带参数/选项,你必须在 webpack 配置中,向 plugins 属性传入 new 实例。

根据你的 webpack 用法,这里有多种方式使用插件。

代码语言:javascript复制
webpack.config.js

const HtmlWebpackPlugin = require('html-webpack-plugin'); //通过 npm 安装
const webpack = require('webpack'); //访问内置的插件
const path = require('path');

const config = {
  entry: './path/to/my/entry/file.js',
  output: {
    filename: 'my-first-webpack.bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /.(js|jsx)$/,
        use: 'babel-loader'
      }
    ]
  },
  plugins: [
    new webpack.optimize.UglifyJsPlugin(),
    new HtmlWebpackPlugin({template: './src/index.html'})
  ]
};

module.exports = config;

5.1、HTML Webpack Plugin(创建HTML插件)

该个插件的作用是用来自动生成html页面,既可以生成单个页面又可以生成多个页面,并且在生成前可以给它一些的配置参数,它会按照你想要的生成方式去生成页面。

第一步:安装

代码语言:javascript复制
npm i html-webpack-plugin -D

第二步:在webpack.config.js里引入模块

代码语言:javascript复制
const HtmlWebpackPlugin=require('html-webpack-plugin');

第三步:在webpack.config.js中的plugins对象里new一个实例

代码语言:javascript复制
plugins:[
  new HtmlWebpackPlugin({参数})
]

结果

代码语言:javascript复制
const HtmlWebpackPlugin = require('html-webpack-plugin')
 
module.exports = {
  entry: 'index.js',
  output: {
    path: __dirname   '/dist',
    filename: 'index_bundle.js'
  },
  plugins: [
    new HtmlWebpackPlugin()
  ]
}

参数:

title

生成页面的titile元素

filename

生成的html文件的文件名。默认index.html,可以直接配置带有子目录

代码语言:javascript复制
//webpack.config.js
...
plugins: [
  new HtmlWebpackPlugin({
    ...
    filename: 'index1.html'//可带子目录'html/index1.html'
  })
]

template

模版文件路径

templateParameters

{Boolean|Object|Function} 允许覆盖模板中使用的参数

代码语言:javascript复制
//webpack.config.js
...
plugins: [
  new HtmlWebpackPlugin({
    ...
    templateParameters: {
      title: 'xxxx',
      favicon: './favicon/index.ico',
    }
  })
]

inject

插入的script插入的位置,四个可选值: true: 默认值,script标签位于html文件的body底部 body: 同true headscript标签位于html文件的head标签内 false: 不插入生成的js文件,只是生成的html文件

favicon

为生成的html文件生成一个favicon,属性值是路径

minify

html文件进行压缩。属性值是false或者压缩选项值。默认false不对html文件进行压缩。 html-webpack-plugin中集成的html-minifier,生成模板文件压缩配置,有很多配置项,这些配置项就是minify的压缩选项值。

hash

给生成的js文件尾部添加一个hash值。这个hash值是本次webpack编译的hash值。默认false;

代码语言:javascript复制
//webpack.config.js
...
plugins: [
  new HtmlWebpackPlugin({
    ...
    hash: true
  })
]
//html
<script type="text/javascript" src="bundle.js?59a5ed17040d94df87fe">
//59a5ed17040d94df87fe是本次webpack编译的hash值

cache

Boolean类型。只在文件被修改的时候才生成一个新文件。默认值true

showErrors

Boolean类型。错误信息是否写入html文件。默认true

chunks

html文件中引用哪些js文件,用于多入口文件时。不指定chunks时,所有文件都引用

代码语言:javascript复制
//webpack.config.js
entry: {
  index1: path.resolve(__dirname, './index1.js'),
  index2: path.resolve(__dirname, './index2.js'),
  index3: path.resolve(__dirname, './index3.js')
}
...
plugins: [
  new HtmlWebpackPlugin({
    ...
    chunks: [index1, index2]//html文件中只引入index1.js, index2.js
  })
]

excludeChunks

与chunks相反,html文件不引用哪些js文件

代码语言:javascript复制
//webpack.config.js
entry: {
  index1: path.resolve(__dirname, './index1.js'),
  index2: path.resolve(__dirname, './index2.js'),
  index3: path.resolve(__dirname, './index3.js')
}
...
plugins: [
  new HtmlWebpackPlugin({
    ...
    excludeChunks: [index3.js]//html文件中不引入index3.js
  })
]

chunksSortMode

控制script标签的引用顺序。默认五个选项: none: 无序 auto: 默认值, 按插件内置的排序方式 dependency: 根据不同文件的依赖关系排序 manual: chunks按引入的顺序排序, 即属性chunks的顺序 {Function}: 指定具体的排序规则

xhtml

Boolean类型,默认falsetrue时以兼容xhtml的模式引用文件

示例:

代码语言:javascript复制
plugins:[
    new HtmlWebpackPlugin({
        title:'Hello app',    /*这个值对应html里的title*/
        template:'./src/template.html', //模板文件地址
        filename:'test1.html',  //文件名,默认为index.html(路径相对于output.path的值)
        inject:true,    //script标签的位置,true/body为在</body>标签前,head为在<head>里,false表示页面不引入js文件
        hash:true,  //是否为引入的js文件添加hash值
        chunks:['one'], //页面里要引入的js文件,值对应的是entry里的key。省略参数会把entry里所有文件都引入
        //excludeChunks:['one'],//页面里不能引入的js文件,与chunks刚好相反
        minify:{    //html-webpack-plugin内部集成了html-minifier
            collapseWhitespace:true,    //压缩空格
            removeAttributeQuotes:true, //移除引号
            removeComments:true,        //移除注释
        },
    }),
    //生成两个文件,分别引入两个js文件(现在是一个文件里引入了两个js)
    new HtmlWebpackPlugin({
        title:'kaivon',
        template:'./src/template.html',
        hash:true,
        filename:'test2.html',
        chunks:['two']
    })
]

示例1:

webpack.config03.js配置文件

代码语言:javascript复制
//webpack配置文件

//导入用于生成html的插件
const HtmlWebpackPlugin=require("html-webpack-plugin");

//依赖node中的path模块
var path=require('path');

//定义一个默认模块对象
module.exports={
    entry:{app01:"./src/app03.js"},
    //设置输出结果
    output: {
        //路径,将相对路径转绝对路径
        path:path.resolve(__dirname,'dist'),
        //文件,[name]是模块名称,占位
        filename: "[hash:8].bundle.js"
    },
    module: {
    },
    plugins: [
        //创建一个插件对象,并指定参数
        new HtmlWebpackPlugin({
            //指定生成的文件路径与名称
            filename:"../app03.html",
            //标题
            title:"Hello App03!"
        })
    ]
};

arc/app03.js

代码语言:javascript复制
alert("Hello App03!");

打包结果:

运行:

示例2:

webpack.config03.js配置文件

代码语言:javascript复制
//webpack配置文件

//导入用于生成html的插件
const HtmlWebpackPlugin=require("html-webpack-plugin");

//依赖node中的path模块
var path=require('path');

//定义一个默认模块对象
module.exports={
    entry:{app01:"./src/app03.js"},
    //设置输出结果
    output: {
        //路径,将相对路径转绝对路径
        path:path.resolve(__dirname,'dist'),
        //文件,[name]是模块名称,占位
        filename: "[hash:8].bundle.js"
    },
    module: {
    },
    plugins: [
        //创建一个插件对象,并指定参数
        new HtmlWebpackPlugin({
            //指定生成的文件路径与名称
            filename:"../app03.html",
            //标题
            title:"Hello App03!",
            //指定模板
            template:"./templates/tmpl03.html",
            //模板参数,允许覆盖templates中的参数
            templateParameters:{
                content:"Hello templateParameters!",
                //重写title
                title:"Hello App03 title!",
                key:"value"
            },
            minify:{
                removeComments:true,  //移除注释
                collapseWhitespace:true,  //折叠空格
                //更新请参数https://github.com/kangax/html-minifier#options-quick-reference
            }
        })
    ]
};

/templates/tmpl03.html 模板文件

代码语言:javascript复制
<!DOCTYPE html>
<html>
<head>




















    <meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
    <title><%=title %></title>
</head>
<body>
<!--这是一个标题-->
<h2>
    <%=content%>
</h2>

</body>
</html>

生成结果:

5.2、Mini-css-extract-plugin(单独提取CSS插件)

将CSS提取为独立的文件的插件,对每个包含css的js文件都会创建一个CSS文件,支持按需加载css和sourceMap

默认情况下css是被js注入的一段style,如下所示:

只能用在webpack4中,对比另一个插件 extract-text-webpack-plugin特点:

  • 异步加载
  • 不重复编译,性能更好
  • 更容易使用
  • 只针对CSS

目前缺失功能,HMR(热模块替换)。

代码语言:javascript复制
全称是Hot Module ReplaceMent(HMR),理解成热模块替换或者模块热替换都可以吧,和.net中的热插拔一个意思,就是在运行中对程序的模块进行更新。这个功能主要是用于开发过程中,对生产环境没有任何帮助(这一点区别.net热插拔)。效果上就是界面的无刷新更新。

HMR基于WDS,style-loader可以通过它来实现无刷新更新样式。但是对于JavaScript模块就需要做一点额外的处理,怎么处理继续往下看。因为HMR是用于开发环境的,所以我们修改下配置,做两份准备。一个用于生产,一个用于开发。

安装:

代码语言:javascript复制
npm install --save-dev mini-css-extract-plugin

使用:

代码语言:javascript复制
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  plugins: [
    new MiniCssExtractPlugin({
      // 类似 webpackOptions.output里面的配置 可以忽略
      filename: '[name].css',
      chunkFilename: '[id].css',
    }),
  ],
  module: {
    rules: [
      {
        test: /.css$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
            options: {
              // 这里可以指定一个 publicPath
              // 默认使用 webpackOptions.output中的publicPath
              publicPath: '../'
            },
          },
          'css-loader',
        ],
      }
    ]
  }
}

高级配置:

这个插件应该只用在 production 配置中,并且在loaders链中不使用 style-loader, 特别是在开发中使用HMR,因为这个插件暂时不支持HMR

代码语言:javascript复制
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const devMode = process.env.NODE_ENV !== 'production';

module.exports = {
  plugins: [
    new MiniCssExtractPlugin({
      filename: devMode ? '[name].css' : '[name].[hash].css',
      chunkFilename: devMode ? '[id].css' : '[id].[hash].css',
    })
  ],
  module: {
    rules: [
      {
        test: /.(sa|sc|c)ss$/,
        use: [
          devMode ? 'style-loader' : MiniCssExtractPlugin.loader,
          'css-loader',
          'postcss-loader',
          'sass-loader',
        ],
      }
    ]
  }
}

示例:

webpack.config03.js

代码语言:javascript复制
//webpack配置文件

//导入用于生成html的插件
const HtmlWebpackPlugin=require("html-webpack-plugin");
//导入用于提取css的插件
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

//依赖node中的path模块
var path=require('path');

//定义一个默认模块对象
module.exports={
    entry:{app01:"./src/app03.js"},
    //设置输出结果
    output: {
        //路径,将相对路径转绝对路径
        path:path.resolve(__dirname,'dist'),
        //文件,[name]是模块名称,占位
        filename: "[hash:8].bundle.js"
    },
    module: {
        rules: [
            {
                test: /.scss$/,
                use: [{
                    loader:MiniCssExtractPlugin.loader //提取css并link
                }, {
                    loader: "css-loader" // 将 CSS 转化成 CommonJS 模块
                }, {
                    loader: "sass-loader" // 将 Scss 编译成 CSS
                }]
            }
        ]
    },
    plugins: [
        //创建一个插件对象,并指定参数
        new HtmlWebpackPlugin({
            //指定生成的文件路径与名称
            filename:"../app03.html",
            //标题
            title:"Hello App03!",
            //指定模板
            template:"./templates/tmpl03.html",
            //模板参数,允许覆盖templates中的参数
            templateParameters:{
                content:"Hello templateParameters!",
                //重写title
                title:"Hello App03 title!",
                key:"value"
            },
            minify:{
                removeComments:true,  //移除注释
                collapseWhitespace:true,  //折叠空格
                //更新请参数https://github.com/kangax/html-minifier#options-quick-reference
            }
        }),
        //创建一个用于提取css的插件对象
        new MiniCssExtractPlugin({
            filename:"[name]_[hash:10].css",
            chunkFilename:"[id]"
        })
    ]
};

src/app03.js

代码语言:javascript复制
import '../css/baseScss.scss';

alert("Hello App03!");

打包生成的结果

代码语言:javascript复制
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
    <title>Hello App03 title!</title>
    <link href="dist/app01_1b7c11aa92.css" rel="stylesheet">
</head>
<body><h2>Hello templateParameters!</h2>
<script type="text/javascript" src="dist/1b7c11aa.bundle.js"></script>
</body>
</html>

运行结果:

5.3、clean-webpack-plugin(删除或清理构建目录)

在用HtmlWebpackPlugin的时候时需要把dist目录删掉再去看生成的文件,clean-webpack-plugin这个插件就可以做这件事情

第一步:安装

代码语言:javascript复制
npm i clean-webpack-plugin --save-dev

第二步:在webpack.config.js里引入模块

代码语言:javascript复制
const CleanWebpackPlugin=require('clean-webpack-plugin');

第三步:在plugins的最前面创建清理对象

代码语言:javascript复制
plugins:[
new CleanWebpackPlugin(['./dist']), //这个一定要放在最上面,作用是先删除dist目录再创建新的dist目录。里面的参数为要删除的目录,放在一个数组里面
...
]

在文件夹里打开dist所在的目录,并在终端里再次执行命令webpack后,会看到dist目录先被删除后又被创建。

关于clean-webpack-plugin插件的所有配置参数请参考:https://www.npmjs.com/package/clean-webpack-plugin

该插件有两个参数:

Paths ( 必须)

An [array] of string paths to clean

代码语言:javascript复制
[
  'dist',         // removes 'dist' folder
  'build/*.*',    // removes all files in 'build' folder
  'web/*.js'      // removes all JavaScript files in 'web' folder
]

Options and defaults (可选)

代码语言:javascript复制
{
  // Absolute path to your webpack root folder (paths appended to this)
  // Default: root of your package
  root: __dirname,
 
  // Write logs to console.
  verbose: true,
  
  // Use boolean "true" to test/emulate delete. (will not remove files).
  // Default: false - remove files
  dry: false,           
 
  // If true, remove files on recompile. 
  // Default: false
  watch: false,
 
  // Instead of removing whole path recursively,
  // remove all path's content with exclusion of provided immediate children.
  // Good for not removing shared files from build directories.
  exclude: [ 'files', 'to', 'ignore' ],
 
  // allow the plugin to clean folders outside of the webpack root.
  // Default: false - don't allow clean folder outside of the webpack root
  allowExternal: false
  
  // perform clean just before files are emitted to the output dir
  // Default: false
  beforeEmit: false
}

示例:

代码语言:javascript复制
const CleanWebpackPlugin = require('clean-webpack-plugin'); //installed via npm
const HtmlWebpackPlugin = require('html-webpack-plugin'); //installed via npm
const webpack = require('webpack'); //to access built-in plugins
const path = require('path');
 
// the path(s) that should be cleaned
let pathsToClean = [
  'dist',
  'build'
]
 
// the clean options to use
let cleanOptions = {
  root:     '/full/webpack/root/path',
  exclude:  ['shared.js'],
  verbose:  true,
  dry:      false
}
 
// sample WebPack config
const webpackConfig = {
  entry: './path/to/my/entry/file.js',
  output: {
    filename: 'my-first-webpack.bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /.(js|jsx)$/,
        loader: 'babel-loader'
      }
    ]
  },
  plugins: [
    new CleanWebpackPlugin(pathsToClean, cleanOptions),
    new webpack.optimize.UglifyJsPlugin(),
    new HtmlWebpackPlugin({template: './src/index.html'})
  ]
}

示例:

webpack.config03.js

代码语言:javascript复制
//webpack配置文件

//导入用于理清目录的插件
const CleanWebpackPlugin=require('clean-webpack-plugin');
//导入用于生成html的插件
const HtmlWebpackPlugin=require("html-webpack-plugin");
//导入用于提取css的插件
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

//依赖node中的path模块
var path=require('path');

//定义一个默认模块对象
module.exports={
    entry:{app01:"./src/app03.js"},
    //设置输出结果
    output: {
        //路径,将相对路径转绝对路径
        path:path.resolve(__dirname,'dist'),
        //文件,[name]是模块名称,占位
        filename: "[hash:8].bundle.js"
    },
    module: {
        rules: [
            {
                test: /.scss$/,
                use: [{
                    loader:MiniCssExtractPlugin.loader //提取css并link
                }, {
                    loader: "css-loader" // 将 CSS 转化成 CommonJS 模块
                }, {
                    loader: "sass-loader" // 将 Scss 编译成 CSS
                }]
            }
        ]
    },
    plugins: [
        //创建一个清理插件,参数一为目标,如路径,参数二为选项
        new CleanWebpackPlugin(['dist'],{dry:false}),
        //创建一个插件对象,并指定参数
        new HtmlWebpackPlugin({
            //指定生成的文件路径与名称
            filename:"../app03.html",
            //标题
            title:"Hello App03!",
            //指定模板
            template:"./templates/tmpl03.html",
            //模板参数,允许覆盖templates中的参数
            templateParameters:{
                content:"Hello templateParameters!",
                //重写title
                title:"Hello App03 title!",
                key:"value"
            },
            minify:{
                removeComments:true,  //移除注释
                collapseWhitespace:true,  //折叠空格
                //更新请参数https://github.com/kangax/html-minifier#options-quick-reference
            }
        }),
        //创建一个用于提取css的插件对象
        new MiniCssExtractPlugin({
            filename:"[name]_[hash:10].css",
            chunkFilename:"[id]"
        })
    ]
};

运行前:

运行后:

5.4、常用plugins

5.4.1、用于修改行为

  • define-plugin:定义环境变量
  • context-replacement-plugin:修改 require 语句在寻找文件时的默认行为。
  • ignore-plugin:用于忽略部分文件。

5.4.2、用于优化

  • commons-chunk-plugin:提取公共代码
  • extract-text-webpack-plugin:提取 JavaScript 中的 CSS 代码到单独的文件中
  • prepack-webpack-plugin:通过 Facebook 的 Prepack 优化输出的 JavaScript 代码性能
  • uglifyjs-webpack-plugin:通过 UglifyES 压缩 ES6 代码
  • webpack-parallel-uglify-plugin:多进程执行 UglifyJS 代码压缩,提升构建速度。
  • imagemin-webpack-plugin:压缩图片文件。
  • webpack-spritesmith:用插件制作雪碧图。
  • ModuleConcatenationPlugin:开启 Webpack Scope Hoisting 功能
  • dll-plugin:借鉴 DDL 的思想大幅度提升构建速度
  • hot-module-replacement-plugin:开启模块热替换功能。

5.4.3、其它

  • serviceworker-webpack-plugin:给网页应用增加离线缓存功能
  • stylelint-webpack-plugin:集成 stylelint 到项目中
  • i18n-webpack-plugin:给你的网页支持国际化。
  • provide-plugin:从环境中提供的全局变量中加载模块,而不用导入对应的文件。
  • web-webpack-plugin:方便的为单页应用输出 HTML,比 html-webpack-plugin 好用。

六、DevServer开发服务器

webpack-dev-server就是一个基于Node.js和webpack的一个简易服务器。它在服务器端使用webpack-dev-middleware进行webpack构建打包;并在客户端注入一份runtime,用于接受服务器端的构建打包后信息。

在实际开发中我们可能会需要完成如下功能:

  1. 提供 HTTP 服务而不是使用本地文件预览;
  2. 监听文件的变化并自动刷新网页,做到实时预览;
  3. 支持 Source Map,以方便调试。

Webpack 原生支持上述第2、3点内容,再结合官方提供的开发工具 DevServer 也可以很方便地做到第1点。 DevServer 会启动一个 HTTP 服务器用于服务网页请求,同时会帮助启动 Webpack ,并接收 Webpack 发出的文件更变信号,通过 WebSocket 协议自动刷新网页做到实时预览。

6.1、快速开启DevServer

安装 DevServer:

代码语言:javascript复制
npm i -D webpack-dev-server

安装成功后在项目的根目录下执行webpack-dev-server 命令, DevServer 服务器就启动了,这时你会看到控制台有一串日志输出:

Project is running at http://localhost:8080/

webpack output is served from /

现在就可以直接访问了

这意味着 DevServer 启动的 HTTP 服务器监听在 http://localhost:8080/ ,DevServer 启动后会一直驻留在后台保持运行,访问这个网址你就能获取项目根目录下的 index.html。 

注意:

1、此时可能会提示webpack-dev-server不是内部命令,解决办法为在全局再次安装一下webpack-dev-server模块,或者在package.json里的scripts里加上"dev": "webpack-dev-server",然后执行命令npm run dev

2、并没有通过webpack命令生成一个dist目录,然后在浏览器里输入地址http://localhost:8080/后,页面会正常显示。这个原因是devServer会将webpack构建出的文件保存到内存里,不需要打包生成就能预览

6.2、参数设置

在webpack.config.js中可以根据需要配置dev-server满足你更多的需求。

代码语言:javascript复制
// webpack.config.js 配置一下 devServer
devServer: {
    clientLogLevel: 'warning',
    historyApiFallback: true,
    hot: true,
    compress: true,
    host: 'localhost',
    port: 8080
  }

Hot (文档)

  • 热模块更新作用。即修改或模块后,保存会自动更新,页面不用刷新呈现最新的效果。
  • 这不是和 webpack.HotModuleReplacementPlugin (HMR) 这个插件不是一样功能吗?是的,不过请注意了,HMR 这个插件是真正实现热模块更新的。而 devServer 里配置了 hot: true , webpack会自动添加 HMR 插件。所以模块热更新最终还是 HMR 这个插件起的作用。

host (文档)

  • 写主机名的。默认 localhost

port (文档)

  • 端口号。默认 8080

historyApiFallback (文档)

  • 如果为 true ,页面出错不会弹出 404 页面。
  • 如果为 {...} , 看看一般里面有什么。
    • rewrites
    • verbose:如果 true ,则激活日志记录。
    • disableDotRule: 禁止 url 带小数点 . 。
代码语言:javascript复制
rewrites: [
    { from: /^/subpage/, to: '/views/subpage.html' },
    {
      from: /^/helloWorld/.*$/,
      to: function() {
          return '/views/hello_world.html;
      }
    }
]
// 从代码可以看出 url 匹配正则,匹配成功就到某个页面。
// 并不建议将路由写在这,一般 historyApiFallback: true 就行了。 

compress (文档)

  • 如果为 true ,开启虚拟服务器时,为你的代码进行压缩。加快开发流程和优化的作用。

contentBase (文档)

  • 你要提供哪里的内容给虚拟服务器用。这里最好填 绝对路径
  • 默认情况下,它将使用您当前的工作目录来提供内容。
代码语言:javascript复制
// 单目录
contentBase: path.join(__dirname, "public")

// 多目录
contentBase: [path.join(__dirname, "public"), path.join(__dirname, "assets")]

Open (文档)

  • true,则自动打开浏览器。

overlay (文档)

  • 如果为 true ,在浏览器上全屏显示编译的errors或warnings。默认 false (关闭)
  • 如果你只想看 error ,不想看 warning
代码语言:javascript复制
overlay:{
    errors:true,
    warnings:false
}

quiet (文档)

  • true,则终端输出的只有初始启动信息。 webpack 的警告和错误是不输出到终端的。

publicPath (文档)

  • 配置了 publicPath后, url = '主机名' 'publicPath配置的' '原来的url.path'。这个其实与 output.publicPath 用法大同小异。
  • output.publicPath 是作用于 js, css, img 。而 devServer.publicPath 则作用于请求路径上的。
代码语言:javascript复制
// devServer.publicPath
publicPath: "/assets/"

// 原本路径 --> 变换后的路径
http://localhost:8080/app.js --> http://localhost:8080/assets/app.js

proxy (文档)

  • 当您有一个单独的API后端开发服务器,并且想要在同一个域上发送API请求时,则代理这些 url 。看例子好理解。
代码语言:javascript复制
  proxy: {
    '/proxy': {
        target: 'http://your_api_server.com',
        changeOrigin: true,
        pathRewrite: {
            '^/proxy': ''
        }
  }
  1. 假设你主机名为 localhost:8080 , 请求 API 的 url 是 http://your_api_server.com/user/list
  2. '/proxy':如果点击某个按钮,触发请求 API 事件,这时请求 url 是http://localhost:8080/proxy/user/list 。
  3. changeOrigin:如果 true ,那么 http://localhost:8080/proxy/user/list 变为 http://your_api_server.com/proxy/user/list 。但还不是我们要的 url 。
  4. pathRewrite:重写路径。匹配 /proxy ,然后变为'' ,那么 url 最终为 http://your_api_server.com/user/list 。

watchOptions (文档)

  • 一组自定义的监听模式,用来监听文件是否被改动过。
代码语言:javascript复制
watchOptions: {
  aggregateTimeout: 300,
  poll: 1000,
  ignored: /node_modules/
}
  1. aggregateTimeout:一旦第一个文件改变,在重建之前添加一个延迟。填以毫秒为单位的数字。
  2. ignored:观察许多文件系统会导致大量的CPU或内存使用量。可以排除一个巨大的文件夹。
  3. poll:填以毫秒为单位的数字。每隔(你设定的)多少时间查一下有没有文件改动过。不想启用也可以填false
代码语言:javascript复制
var path = require("path");
    var webpack = require("webpack");
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    module.exports = {
        mode:"development",
        entry:{
            app:"./src/js/main.js"
        },
        output:{
            filename: "bundle.js",
            path:path.resolve(__dirname,"../dist"),
            //path.resolve是nodejs里的方法,具体看nodejs api
        },
         devServer:{
            contentBase:false,
            //我这里没有设置contentBase,contentBase必须指向存在的bundle.js文件所在目录,
            //因为这里是开发模式,所以dist目录并不存在,所以用false.
            host:'localhost',
            port:'8888',
            inline:true,//webpack官方推荐
            watchOptions: {
                aggregateTimeout: 2000,//浏览器延迟多少秒更新
                poll: 1000//每秒检查一次变动
            },
            compress:true,//一切服务都启用gzip 压缩
            historyApiFallback:true,//找不到页面默认跳index.html
            hot:true,//启动热更新,必须搭配new webpack.HotModuleReplacementPlugin()插件
            open:true,
        },
        plugins: [
            new webpack.HotModuleReplacementPlugin(),
            new HtmlWebpackPlugin({
                template:"index.html",
                title:'index',
                inject: true
            }),
            // new webpack.NamedModulesPlugin(), 
            // HMR shows correct file names in console on update.
            // new webpack.NoEmitOnErrorsPlugin()
        ]
    } 

 示例:

配置文件:

代码语言:javascript复制
//webpack配置文件

//依赖node中的path模块
var path = require('path');

//定义一个默认模块对象
module.exports = {
    //指定入口文件的位置,多入口
    entry: {
        index: "./src/index.js",
        main: "./src/main.js"
    },
    //设置输出结果
    output: {
        //路径,将相对路径转绝对路径
        path: path.resolve(__dirname, 'dist'),
        //文件,[name]是模块名称,占位
        filename: "[name].bundle.js"
    },
    module: {  //模块处理
        rules: [ //处理器
            {
                test: /.txt$/,  //当模块的后缀为.txt时匹配
                use: "raw-loader"  //模块转换器,可以以对象的形式指定参数
            },
            {
                test: /.css$/,  //匹配所有css模块
                //use表示要使用哪个loader,它的值是个数组,loader的使用顺序是从后往前
                use: ["style-loader", {
                    loader: "css-loader",  //转换器名称
                    options: {  //配置选项
                        modules: true,  //模块化
                        sourceMap: true  //是否生成调试文件
                    }
                }]  //使用多个模块转换器
            },
            {
                test: /.scss$/,
                use: [{
                    loader: "style-loader" // 将 JS 字符串生成为 style 节点
                }, {
                    loader: "css-loader" // 将 CSS 转化成 CommonJS 模块
                }, {
                    loader: "sass-loader" // 将 Scss 编译成 CSS
                }]
            }
        ]
    },
    mode: "development",
    devServer: {
        //端口号
        port:8889,
        //运行时打开浏览器测试效果
        open:true,
        //主机地址
        host:"192.168.4.113",
        //是否开启压缩
        compress:true,
        //是否开启热模块替换
        hot:true
    }
};

运行结果:

七、视频

https://www.bilibili.com/video/av37008594/

八、示例

https://git.dev.tencent.com/zhangguo5/WebPackDemo.git

九、作业

(1)、创建一个项目,项目中使用ES6的模块功能与箭头函数,使用babel-loader转译成兼容IE8的前端输出。

0 人点赞