首页

用 webpack 写一个简单的 JS SDK

kkcode
2024-02-26  阅读 144

引言

最近项目中需要提供一个封装的 JS SDK,虽然目前是一个很简单的功能,但是为了日后便于维护,扩展,因此还是选用了 webpack 作为打包工具,作为参考,推荐一篇上好的文章,感兴趣的朋友可以先阅读一下

JavaScript SDK Design Guide (中文翻译: JavaScript SDK 设计指南

目标

作为一个 SDK,我想达到如下的目的

  • 提供一个加载方案
  • 暴露一个公共变量,最好能支持多种加载方式
  • 提供未压缩版与压缩版
  • 可以为不同的合作商提供定制版本
  • 内部实现通过模块引用,方便扩展

接下来一步步讲一下如何通过 webpack 实现

准备

假如我们最后需要提供的 SDK 如下

// 引用
<script type="text/javascript" src="http://xxx.com/sdk.js"></script>

// 使用
window.SDK.Shop.getList()       // 获取门店信息列表
window.SDK.Store.getById()      // 通过ID获取商品信息复制代码

那么文件列表应该大致如下

|
| - package.json
| - webpack.config.js
| - node_modules
| - src
    | - index.js
    | - lib
        | - shop.js
        | - store.js
| - dist
| - build.js复制代码

webpack 通过 index.js 入口打包好文件,放到 dist 文件夹,一些关键文件的代码应该如下

webpack.config.js

let path = require('path')
let webpack = require('webpack')

module.exports = {
    entry: {
        'sdk': ['./src/index.js']
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js'
    }, 
    // 压缩混淆 js
    plugins: [
        new webpack.optimize.UglifyJsPlugin({
            compress: {
                warnings: false
            },
            sourceMap: true
        })
    ]
}复制代码

shop.js

module.exports = {
    getList: function () {
        .....
    }
}复制代码

store.js

module.exports = {
    getById: function (id) {
        .....
    }
}复制代码

index.js

var Shop = require('./lib/shop.js')
var Store = require('./lib/store.js')

module.exports = {
    Shop: Shop,
    Store: Store
}复制代码

build.js

// 这里简单的 copy 了 vue-cli 提供的 build 代码
let webpackConfig = require('./webpack.config')

let rm = require('rimraf')
let path = require('path')
let webpack = require('webpack')
let util = require('util')

const compileCallback = (er, stats) => {
    if (er) throw er
    stats = util.isArray(stats.stats) ? stats.stats : [stats]
    stats.forEach((item) => {
        process.stdout.write(item.toString({
            colors: true,
            modules: false,
            children: false,
            chunks: false,
            chunkModules: false
        }) + '\n\n')
    })         

    console.log('Build complete.\n')
}

rm(path.resolve(__dirname, './dist'), err => {
    if (err) throw err
    let compiler = webpack(webpackConfig)

    compiler.run(compileCallback)
})复制代码

方案

1. 加载引用

这部分很好实现

  • 提供一个静态文件地址(或者一个 CDN 地址)来简单的通过 html 加载
  • 提供一个 npm 包,在服务器端加载(因为暂时没这种需求,所以先挂起)

2. 暴露一个公共变量

最简单的做法是在 index.js 里加一句 window.SDK = ...

不过 webpack 有更好的解决方案 output.library

output 选项主要用于配置文件输出规则,而 output.library 选项可以用于输出时将文件暴露为一个变量,可以说是为了打包 SDK 文件而生的一个配置项

这里还有一个 webpack 的教程来帮你如何使用 library 创建 Library

另一个选项 output.libraryTarget 则可以配置如何输出变量,默认值是 var

简单的说明一下这些值的含义

  • var:在当前作用域导出一个变量
  • assign: 导出一个变量作为全局变量
  • this: 导出作为 this 的一个属性,这个 this 不一定是 window ,要看引用 SDK 的位置
  • window: 导出为 window 的一个属性,基本上就算全局变量了
  • global:导出为 global 的一个属性,估计是这个变量名比较常用吧。。
  • commonjs:导出为 exports 的一个属性,导出的格式可以在 CommonJS 环境里引用
  • commonjs2:赋值给 module.exports ,同样可以用在 CommonJS 环境里
  • amd:暴露给 AMD 模块
  • umd:暴露为所有模块都可用的格式
  • jsonp:包裹到一个 jsonp 包装容器中,也就是一个 Function

因此,我们稍微修改一下 webpack.config.js 的代码

module.exports = {
    ...
    output: {
        path: './dist',
        filename: '[name].js',
        library: 'SDK',
        libraryTarget: "umd"
    }
    ...
}复制代码

3. 提供两个版本

简单的可以写两个 build 脚本,分别打包为压缩代码与未压缩代码,不过 webpack 本身也可以导出为多个配置(这也摸清了 webpack 如何为多个 output 配置不同的参数)

于是,我们的 webpack.config.js 代码修改为

module.exports = [
  // 未压缩版
  {
        entry: {
            'sdk': './src/index.js'
        },
        output: {
            path: path.resolve(__dirname, 'dist'),
            filename: '[name].js',
            library: 'SDK',
            libraryTarget: "umd"
        }
  },

  // 压缩版
  {
        entry: {
            'sdk.min': './src/index.js'
        },
        output: {
            path: path.resolve(__dirname, 'dist'),
            filename: '[name].js',
            library: 'SDK',
            libraryTarget: "umd"
        },
        plugins: [
            new webpack.optimize.UglifyJsPlugin({
                compress: {
                    warnings: false
                },
                sourceMap: true
            })
        ]
  }
]复制代码

打包后的结果如下图所示

4. 提供定制版本

这个相对容易点了,可以通过上一条的方式做多个配置,也可以简单的在 entry 中写多个入口

webpack.config.js 代码修改为

module.exports = [
    {
        entry: {
            'sdk': './src/index.js',
            'custom': './src/custom.js'
        }
        ...
    },
    ...
]复制代码

5. 内部实现通过模块引用

这个就不再啰嗦了... 经过了一系列配置,别说模块了,ES6 都能给你加进去,不过要注意的是,如果引入了 babel 或者其他的库,打包出来的 SDK 文件就很大了,甚至是简单的引用一个 webpack-merge 都会增加 50K 的容量,所以最好还是以原生的方式去写,如果需要 ajax 等功能就简单的封装一下,能不引用别的库就不引用,如果觉得文件体积太大,可以用 webpack-bundle-analyzer 分析一下文件大小的分布,以及是否有重复引用

结语

虽然是个小项目,不过在初期也应该考虑的全面,目前项目虽小,但是不见得以后会发展成什么样,可能有些人会说这么简单的项目用闭包封装一下,暴露两个接口即可,何必搞那么复杂,但是假如之后需要添加新的接口呢,假如需要提供两个 sdk ,分别提供不同的接口,同时又有部分相同的接口呢,如果这时候再进行重构,会不会对线上有很大的影响?需要进行多少测试?这无形之中给我们加大了很多成本与不确定性

希望这篇比较初级的文章能对大家在创建一个 SDK 项目上有所帮助~

原文地址 juejin.cn

本文为作者原创文章,转载无需和我联系,但请注明转载链接。 【前端黑猫】