Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

【Webpack】基础知识与简单配置 #38

Open
Tracked by #6
swiftwind0405 opened this issue Apr 8, 2020 · 1 comment
Open
Tracked by #6

【Webpack】基础知识与简单配置 #38

swiftwind0405 opened this issue Apr 8, 2020 · 1 comment
Labels

Comments

@swiftwind0405
Copy link
Owner

swiftwind0405 commented Apr 8, 2020

构建就是把源代码转换成发布到线上的可执行 JavaScrip、CSS、HTML 代码,包括如下内容:

  • 代码转换:TypeScript 编译成 JavaScript、SCSS 编译成 CSS 等。
  • 文件优化:压缩 JavaScript、CSS、HTML 代码,压缩合并图片等。
  • 代码分割:提取多个页面的公共代码、提取首屏不需要执行部分的代码让其异步加载。
  • 模块合并:在采用模块化的项目里会有很多个模块和文件,需要构建功能把模块分类合并成一个文件。
  • 自动刷新:监听本地源代码的变化,自动重新构建、刷新浏览器。
  • 代码校验:在代码被提交到仓库前需要校验代码是否符合规范,以及单元测试是否通过。
  • 自动发布:更新完代码后,自动构建出线上发布代码并传输给发布系统。
  • 构建其实是工程化、自动化思想在前端开发中的体现,把一系列流程用代码去实现,让代码自动化地执行这- - 一系列复杂的流程。 构建给前端开发注入了更大的活力,解放了我们的生产力。

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

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

image

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

概念

Webpack 有以下几个核心概念。

  • Entry:入口,Webpack 执行构建的第一步将从 Entry 开始,可抽象成输入。比如单页面应用只有一个入口文件,而多页面应用则有多个入口文件。
  • Module:模块,在 Webpack 里一切皆模块,一个模块对应着一个文件。Webpack 会从配置的 Entry 开始递归找出所有依赖的模块。
  • Chunk:代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割。
  • Loader:模块转换器,用于把模块原内容按照需求转换成新内容。webpack 原生只支持 jsjson 两种文件,通过 Loaders 去支持其它文件类型并把它们转化为有效的模块,并且可以添加到依赖图中。
  • Plugin:扩展插件,webpack 的重要组成部分,其 webpack 源码的很多核心功能也都是插件实现的,由于webpack提供了在不同的生命周期内提供了很多不同的hooks钩子函数,插件的工作原理就是在 webpack 构建流程中的不同的(hooks)时机注入扩展逻辑来改变构建结果或做个性化操作。
  • Output:输出结果,在 Webpack 经过一系列处理并得出最终想要的代码后输出结果。
  • mode:指定当前的构建环境是 productiondevelopment 还是 none。设置 mode 可以使用 webpack 内置的函数,默认值为 production

source-map

Webpack 支持为转换生成的代码输出对应的 Source Map 文件,以方便在浏览器中能通过源码调试。 控制 Source Map 输出的 Webpack 配置项是 devtool,它有很多选项

devtool 取值其实可以由 source-mapevalinlinehiddencheapmodule 这六个关键字随意组合而成。 这六个关键字每个都代表一种特性,它们的含义分别是:

  • eval:用 eval 语句包裹需要安装的模块;
  • source-map:生成独立的 Source Map 文件;
  • hidden:不在 JavaScript 文件中指出 Source Map 文件所在,这样浏览器就不会自动加载 Source Map;
  • inline:把生成的 Source Map 转换成 base64 格式内嵌在 JavaScript 文件中;
  • cheap:生成的 Source Map 中不会包含列信息,这样计算量更小,输出的 Source Map 文件更小;同时 Loader 输出的 Source Map 不会被采用;
  • module:来自 Loader 的 Source Map 被简单处理成每行一个模块;

webpack.config.js

module.exports = {
    devtool: "source-map"
    //可以提示比较全面的错误信息,具体到哪一行哪一列第几个字符,并且映射文件会打包到打包文件里面
}
  • 生产环境:默认为 null ,一般不设置( none )或 nosources-source-map

  • 开发环境:默认为 eval ,一般设置为 evalcheap-eval-source-mapcheap-module-eval-source-map

策略为:

  • 使用 cheap 模式可以大幅提高 source map 生成的效率。 没有列信息(会映射到转换后的代码,而不是映射到原始代码),通常我们调试并不关心列信息,而且就算 source map 没有列,有些浏览器引擎(例如 v8) 也会给出列信息。

  • 使用 eval 方式可大幅提高持续构建效率。参考官方文档提供的速度对比表格可以看到 eval 模式的编译速度很快。

  • 使用 module 可支持 babel 这种预编译工具(在 webpack 里做为 loader 使用)。

如果默认的 webpack minimizer 已经被重定义(例如 terser-webpack-plugin ),你必须提供 sourceMap:true 选项来启用 source map 支持。

浏览器缓存与 hash 值

对于我们开发的每一个应用,浏览器都会对静态资源进行缓存,如果我们更新了静态资源,而没有更新静态资源名称(或路径),浏览器就可能因为缓存的问题获取不到更新的资源。在我们使用 webpack 进行打包的时候,webpack 提供了 hash 的概念,所以我们可以使用 hash 来打包。

  • hash
    build-specific, 哈希值对应每一次构建( Compilation ),即每次编译都不同,即使文件内容都没有改变,并且所有的资源都共享这一个哈希值,此时,浏览器缓存就没有用了,可以用在开发环境,生产环境不适用。

  • chunkhash
    chunk-specific, 哈希值对应于 webpack 每个入口点,每个入口都有自己的哈希值。如果在某一入口文件创建的关系依赖图上存在文件内容发生了变化,那么相应入口文件的 chunkhash 才会发生变化,适用于生产环境

  • contenthash
    content-specific,根据包内容计算出的哈希值,只要包内容不变,contenthash 就不变,适用于生产环境。但我们会发现,有时内容没有变更,打包时 [contenthash] 反而变更了的问题,

webpack 也允许哈希的切片。如果你写 [hash:8] ,那么它会获取哈希值的前 8 位。

注意

  • 尽量在生产环境使用哈希

  • 按需加载的块不受 filename 影响,受 chunkFilename 影响

  • 使用 hash/chunkhash/contenthash 一般会配合 html-webpack-plugin (创建 html ,并捆绑相应的打包文件) 、clean-webpack-plugin (清除原有打包文件) 一起使用

如果只是单纯地将所有内容打包成同一个文件,那么 hash 就能够满足了,如果项目涉及到拆包,分模块进行加载等等,那么机需要用 chunkhash,来保证每次更新之后只有相关的文件 hash 值发生改变。
chunkhash 无法在启用 HotModuleReplacementPlugin 的时候使用,只能用 hash

整体配置说明

const path = require("path");

module.exports = {
    // entry 表示 入口,Webpack 执行构建的第一步将从 Entry 开始,可抽象成输入。
    // 类型可以是 string | object | array
    entry: "./app/entry", // 只有1个入口,入口只有1个文件
    entry: ["./app/entry1", "./app/entry2"], // 只有1个入口,入口有2个文件
    entry: {
        // 有2个入口
        a: "./app/entry-a",
        b: ["./app/entry-b1", "./app/entry-b2"],
    },

    // 如何输出结果:在 Webpack 经过一系列处理后,如何输出最终想要的代码。
    output: {
        // 输出文件存放的目录,必须是 string 类型的绝对路径。
        path: path.resolve(__dirname, "dist"),

        // 输出文件的名称
        filename: "bundle.js", // 完整的名称
        filename: "[name].js", // 当配置了多个 entry 时,通过名称模版为不同的 entry 生成不同的文件名称
        filename: "[chunkhash].js", // 根据文件内容 hash 值生成文件名称,用于浏览器长时间缓存文件

        // 发布到线上的所有资源的 URL 前缀,string 类型
        publicPath: "/assets/", // 放到指定目录下
        publicPath: "", // 放到根目录下
        publicPath: "https://cdn.example.com/", // 放到 CDN 上去

        // 导出库的名称,string 类型
        // 不填它时,默认输出格式是匿名的立即执行函数
        library: "MyLibrary",

        // 导出库的类型,枚举类型,默认是 var
        // 可以是 umd | umd2 | commonjs2 | commonjs | amd | this | var | assign | window | global | jsonp ,
        libraryTarget: "umd",

        // 是否包含有用的文件路径信息到生成的代码里去,boolean 类型
        pathinfo: true,

        // 附加 Chunk 的文件名称
        chunkFilename: "[id].js",
        chunkFilename: "[chunkhash].js",

        // JSONP 异步加载资源时的回调函数名称,需要和服务端搭配使用
        jsonpFunction: "myWebpackJsonp",

        // 生成的 Source Map 文件名称
        sourceMapFilename: "[file].map",

        // 浏览器开发者工具里显示的源码模块名称
        devtoolModuleFilenameTemplate: "webpack:///[resource-path]",

        // 异步加载跨域的资源时使用的方式
        crossOriginLoading: "use-credentials",
        crossOriginLoading: "anonymous",
        crossOriginLoading: false,
    },

    // 配置模块相关
    module: {
        rules: [
            // 配置 Loader
            {
                test: /\.jsx?$/, // 正则匹配命中要使用 Loader 的文件
                include: [
                    // 只会命中这里面的文件
                    path.resolve(__dirname, "app"),
                ],
                exclude: [
                    // 忽略这里面的文件
                    path.resolve(__dirname, "app/demo-files"),
                ],
                use: [
                    // 使用那些 Loader,有先后次序,从后往前执行
                    "style-loader", // 直接使用 Loader 的名称
                    {
                        loader: "css-loader",
                        options: {
                            // 给 html-loader 传一些参数
                        },
                    },
                ],
            },
        ],
        noParse: [
            // 不用解析和处理的模块
            /special-library\.js$/, // 用正则匹配
        ],
    },

    // 配置插件
    plugins: [],

    // 配置寻找模块的规则
    resolve: {
        modules: [
            // 寻找模块的根目录,array 类型,默认以 node_modules 为根目录
            "node_modules",
            path.resolve(__dirname, "app"),
        ],
        extensions: [".js", ".json", ".jsx", ".css"], // 模块的后缀名
        alias: {
            // 模块别名配置,用于映射模块
            // 把 'module' 映射 'new-module',同样的 'module/path/file' 也会被映射成 'new-module/path/file'
            module: "new-module",
            // 使用结尾符号 $ 后,把 'only-module' 映射成 'new-module',
            // 但是不像上面的,'module/path/file' 不会被映射成 'new-module/path/file'
            "only-module$": "new-module",
        },
        alias: [
            // alias 还支持使用数组来更详细的配置
            {
                name: "module", // 老的模块
                alias: "new-module", // 新的模块
                // 是否是只映射模块,如果是 true 只有 'module' 会被映射,如果是 false 'module/inner/path' 也会被映射
                onlyModule: true,
            },
        ],
        symlinks: true, // 是否跟随文件软链接去搜寻模块的路径
        descriptionFiles: ["package.json"], // 模块的描述文件
        mainFields: ["main"], // 模块的描述文件里的描述入口的文件的字段名称
        enforceExtension: false, // 是否强制导入语句必须要写明文件后缀
    },

    // 输出文件性能检查配置
    performance: {
        hints: "warning", // 有性能问题时输出警告
        hints: "error", // 有性能问题时输出错误
        hints: false, // 关闭性能检查
        maxAssetSize: 200000, // 最大文件大小 (单位 bytes)
        maxEntrypointSize: 400000, // 最大入口文件大小 (单位 bytes)
        assetFilter: function (assetFilename) {
            // 过滤要检查的文件
            return (
                assetFilename.endsWith(".css") || assetFilename.endsWith(".js")
            );
        },
    },

    devtool: "source-map", // 配置 source-map 类型

    context: __dirname, // Webpack 使用的根目录,string 类型必须是绝对路径

    // 配置输出代码的运行环境
    target: "web", // 浏览器,默认
    target: "webworker", // WebWorker
    target: "node", // Node.js,使用 `require` 语句加载 Chunk 代码
    target: "async-node", // Node.js,异步加载 Chunk 代码
    target: "node-webkit", // nw.js
    target: "electron-main", // electron, 主线程
    target: "electron-renderer", // electron, 渲染线程

    externals: {
        // 使用来自 JavaScript 运行环境提供的全局变量
        jquery: "jQuery",
    },

    stats: {
        // 控制台输出日志控制
        assets: true,
        colors: true,
        errors: true,
        errorDetails: true,
        hash: true,
    },

    devServer: {
        // DevServer 相关的配置
        proxy: {
            // 代理到后端服务接口
            "/api": "http://localhost:3000",
        },
        contentBase: path.join(__dirname, "public"), // 配置 DevServer HTTP 服务器的文件根目录,推荐使用一个绝对路径
        compress: true, // 是否开启 gzip 压缩
        historyApiFallback: true, // 是否开发 HTML5 History API 网页
        hot: true, // 是否开启模块热替换功能
        https: false, // 是否开启 HTTPS 模式
		before: function(app, server) {
			res.json({custom: 'response'});
		},              //在服务内部的所有其他中间件之前, 提供执行自定义中间件的功能。 这可以用来配置自定义处理程序
		disableHostCheck: true, // 设置为 true 时,此选项绕过主机检查
		quiet: true      // 启用 `devServer.quiet` 后,除了初始启动信息之外的任何内容都不会被打印到控制台。这也意味着来自 webpack 的错误或警告在控制台不可见。
    },

    profile: true, // 是否捕捉 Webpack 构建的性能信息,用于分析什么原因导致构建性能不佳

    cache: false, // 是否启用缓存提升构建速度

    watch: true, // 是否开始
    watchOptions: {
        // 监听模式选项
        // 不监听的文件或文件夹,支持正则匹配。默认为空
        ignored: /node_modules/,
        // 监听到变化发生后会等300ms再去执行动作,防止文件更新太快导致重新编译频率太高
        // 默认为300ms
        aggregateTimeout: 300,
        // 判断文件是否发生变化是不停的去询问系统指定文件有没有变化,通过传递 true 开启 polling,或者指定毫秒为单位进行轮询
        poll: 1000,
    },
};

通常你可用如下经验去判断如何配置 Webpack:

  • 想让源文件加入到构建流程中去被 Webpack 控制,配置 entry
  • 想自定义输出文件的位置和名称,配置 output
  • 想自定义寻找依赖模块时的策略,配置 resolve
  • 想自定义解析和转换文件的策略,配置 module,通常是配置 module.rules 里的 Loader
  • 其它的大部分需求可能要通过 Plugin 去实现,配置 plugin

参考文档

@swiftwind0405 swiftwind0405 changed the title 【Day38】Webpack入门基础概念 【Day38】Webpack入门与基础配置 Apr 14, 2020
@swiftwind0405 swiftwind0405 changed the title 【Day38】Webpack入门与基础配置 【Day38】Webpack 入门与基础配置 Apr 14, 2020
@swiftwind0405 swiftwind0405 pinned this issue Apr 14, 2020
@swiftwind0405 swiftwind0405 unpinned this issue Apr 14, 2020
@swiftwind0405 swiftwind0405 pinned this issue Apr 15, 2020
@swiftwind0405 swiftwind0405 unpinned this issue Apr 15, 2020
@swiftwind0405
Copy link
Owner Author

swiftwind0405 commented Apr 15, 2020

  • 热更新原理分析
    image

image
https://time.geekbang.org/course/detail/100028901-98391

[Webpack HMR 原理解析]
(https://zhuanlan.zhihu.com/p/30669007)

@swiftwind0405 swiftwind0405 changed the title 【Day38】Webpack 入门与基础配置 【Day38】【Webpack】入门与基础配置 Apr 17, 2020
@swiftwind0405 swiftwind0405 changed the title 【Day38】【Webpack】入门与基础配置 【Webpack】【入门】基础知识与简单配置 Apr 17, 2020
@swiftwind0405 swiftwind0405 changed the title 【Webpack】【入门】基础知识与简单配置 【Webpack】基础知识与简单配置 Apr 29, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant