Шуцзян надеется доставить удовольствие от внешнего интерфейса всем. Эта статья была включенаGitHub.com/маленькое дерево М…звездочка если хочешь ✨
В ежедневной разработке, особенно на промежуточных и фоновых страницах управления, часто используются некоторые часто используемые функции, такие как: защита от сотрясений и регулирование, локальное хранилище, форматирование времени и т. д., но по мере того, как проект продолжает расти, возможность повторного использования и универсальность стала Это стало очень важным вопросом.Как уменьшить операцию копирования и публикации, то есть инкапсулировать ее в набор инструментов, подходящий для нескольких проектов, и управлять им с помощью npm.Метод «Установка на U-диск» может повысить эффективность работы команды, то Сегодня я расскажу о ссылках, задействованных в разработке простой библиотеки инструментов, см. рисунок ниже👇
1. Структура проекта
Какая конфигурация нужна для разработки библиотеки инструментов, ниже случай простой библиотеки инструментов (kdutil) я написал👇
Участвуют:
- build : используется для хранения файлов конфигурации упаковки
- dist : используется для хранения скомпилированных файлов
- src: хранить исходный код (включая запись каждого модуля и определение констант)
- test: хранить тестовые случаи
- babel.config.js : настроить преобразование кода версии ES2015 в совместимый синтаксис JavaScript.
- package.json : определяет конфигурацию и информацию о зависимостях пакета.
- README.md: знакомит с использованием всего инструментария и включенных функций.
2. Способ упаковки
Зачем нужно упаковывать? Инструментальная библиотека предполагает многомодульную разработку, а ремонтопригодность одного модуля нужно сохранить.Во-вторых, чтобы решить проблему, что некоторые младшие версии браузеров не поддерживают синтаксис es6, их нужно преобразовать в синтаксис es5 для использование браузера.В проекте используется веб-пакет в качестве внешнего инструмента упаковки.
2.1 конфигурационный файл веб-пакета
// webpack.pro.config.js
const webpack = require('webpack');
const path = require('path');
const {name} = require('../package.json');
const rootPath = path.resolve(__dirname, '../');
module.exports = {
mode: 'production',
entry: {
kdutil: path.resolve(rootPath, 'src/index.js'),
},
output: {
filename: `[name].min.js`,
path: path.resolve(rootPath, 'dist'),
library: `${name}`,
libraryTarget: "umd"
},
module: {
rules: [
{
test: /\.js$/,
loader: "babel-loader",
exclude: /node_modules/
},
]
},
plugins: [
new webpack.optimize.ModuleConcatenationPlugin()
# 启用作用域提升,作用是让代码文件更小、运行的更快
]
};
Разбор конфигурации:
- запись: определение упакованного файла записи
- плагины: обрабатываются путем введения плагинов, используются для преобразования определенного типа модуля, могут обрабатывать: упаковку, сжатие, переопределение переменных и т. д.
- loader — обрабатывает языки, которые браузеры не могут запускать напрямую, и может конвертировать все типы файлов в допустимые модули, которые может обрабатывать webpack (как показано на рисунке выше, babel-loader используется для конвертации браузеров, несовместимых с записью es6) Распространенными загрузчиками являются TypeScript, Sass, Less, Stylus и т. д.)
- output : конфигурация входного файла, path относится к выходному пути, file относится к окончательному имени выходного файла, наиболее важными из них являются libraryTarget и library, см. следующую главу.
2.1 веб-пакет для libraryTarget и атрибутов библиотеки в библиотеке классов разработки
Потому что в общих проектах SPA при использовании webpack не нужно обращать внимание на эти два свойства, но если вы разрабатываете библиотеку классов, то эти два свойства необходимо понимать.
Существует несколько распространенных форм libraryTarget 👇:
-
libraryTarget: "var" (по умолчанию): библиотека экспортирует значение как объявление переменной (при использовании тега скрипта оно будет доступно в глобальной области видимости после выполнения)
-
LibraryTarget: «Окно»: когда библиотека загружается, возвращаемое значение будет назначено на окно объекта.
-
LibbleTarget: «Commonjs»: Когда библиотека загружается, возвращаемое значение будет назначено на объект экспорта, это имя также означает, что модуль используется в среде Commonjs (среда узла)
-
libraryTarget: "umd" : это способ сделать вашу библиотеку работоспособной при всех определениях модуля. Он будет работать под CommonJS, AMD (в настоящее время библиотека использует 🚀)
Библиотека указывает имя модуля, когда вы требуете или импортируете
2.3 Другие упаковочные инструменты
- Свернуть:Портал 🚪
3. Модульная разработка
Библиотека инструментов содержит несколько функциональных модулей, таких как localstorage, date, http и т. д., и разными функциональными модулями нужно управлять отдельно.Наконец, используйте webpack для разбора require.context() и создания собственного контекста через require. Функция context (). Экспортируйте все модули, ниже приведены все модули, включенные в библиотеку инструментов kdutil👇
3.1 локальный модуль хранения localstorage
localStorage — это новая функция Html5. Используется как локальное хранилище, что решает проблему нехватки места для хранения файлов cookie. Обычные браузеры в localStorage поддерживают размер 5M.
/*
@file: localStorage 本地存储
@Author: tree
*/
module.exports = {
get: function (name) {
if (!name) return;
return window.localStorage.getItem(name);
},
set: function (name, content) {
if (!name) return;
if (typeof content !== 'string') {
content = JSON.stringify(content);
}
window.localStorage.setItem(name, content);
},
delete: function (name) {
if (!name) return;
window.localStorage.removeItem(name);
}
};
3.2 модуль форматирования даты и времени
Часто бывает необходимо отформатировать время в ежедневной разработке, например, установить время на 2019-04-03 23:32:32.
/*
* @file date 格式化
* @author:tree
* @createBy:@2020.04.07
*/
module.exports = {
/**
* 格式化现在的已过时间
* @param startTime {Date}
* @return {String}
*/
formatPassTime: function (startTime) {
let currentTime = Date.parse(new Date()),
time = currentTime - startTime,
day = parseInt(time / (1000 * 60 * 60 * 24)),
hour = parseInt(time / (1000 * 60 * 60)),
min = parseInt(time / (1000 * 60)),
month = parseInt(day / 30),
year = parseInt(month / 12);
if (year) return year + "年前";
if (month) return month + "个月前";
if (day) return day + "天前";
if (hour) return hour + "小时前";
if (min) return min + "分钟前";
else return '刚刚';
},
/**
* 格式化时间戳
* @param time {number} 时间戳
* @param fmt {string} 格式
* @return {String}
*/
formatTime: function (time, fmt = 'yyyy-mm-dd hh:mm:ss') {
let ret;
let date = new Date(time);
let opt = {
"y+": date.getFullYear().toString(),
"M+": (date.getMonth() + 1).toString(), //月份
"d+": date.getDate().toString(), //日
"h+": date.getHours().toString(), //小时
"m+": date.getMinutes().toString(), //分
"s+": date.getSeconds().toString(), //秒
};
for (let k in opt) {
ret = new RegExp("(" + k + ")").exec(fmt);
if (ret) {
fmt = fmt.replace(ret[1], (ret[1].length === 1) ? (opt[k]) : (opt[k].padStart(ret[1].length, "0")))
}
}
return fmt;
}
};
3.3 Инструменты, часто используемые модули управления функциями
Модуль инструментов содержит некоторые часто используемые инструментальные функции, в том числе функцию подавления сотрясений, глубокое копирование, регулярное суждение о типе и т. д., а более общие инструментальные функции будут добавлены позже, и постепенно lodash, на который первоначально полагался проект, станет последовательный, модуль Библиотека оптимизированных, высокопроизводительных утилит JavaScript) для удаления
/*
@file: tools 常用的工具函数
@Author:tree
*/
module.exports = {
/**
* 递归 深拷贝
* @param data: 拷贝的数据
*/
deepCopyBy: function (data) {
const t = getType(data);
let o;
if (t === 'array') {
o = [];
} else if (t === 'object') {
o = {};
} else {
return data;
}
if (t === 'array') {
for (let i = 0; i < data.length; i++) {
o.push(deepCopy(data[i]));
}
} else if (t === 'object') {
for (let i in data) {
o[i] = deepCopy(data[i]);
}
}
return o;
},
/**
* JSON 深拷贝
* @param data: 拷贝的数据
* @return data Object 复制后生成的对象
*/
deepCopy: function (data) {
return JSON.parse(JSON.stringify(data));
},
/**
* 根据类型返回正则
* @param str{string}: 检测的内容
* @param type{string}: 检测类型
*/
checkType: function (str, type) {
const regexp = {
'ip': /((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}/.test(str),
'port': /^(\d|[1-5]\d{4}|6[1-4]\d{3}|65[1-4]\d{2}|655[1-2]\d|6553[1-5])$/.test(str),
'phone': /^1[3|4|5|6|7|8][0-9]{9}$/.test(str), //手机号
'number': /^[0-9]+$/.test(str), //是否全数字,
'email': /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/.test(str),
'IDCard': /^(^[1-9]\d{7}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}$)|(^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])((\d{4})|\d{3}[Xx])$)$/.test(str),
'url': /[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/i.test(str)
};
return regexp[type];
},
/**
* 将手机号中间部分替换为星号
* @param phone{string}: 手机号码
*/
formatPhone: function (phone) {
return phone.replace(/(\d{3})\d{4}(\d{4})/, "$1****$2");
},
/**
* 防抖
* @param func {*} 执行函数
* @param wait {*} 节流时间,毫秒
*/
debounce: (func, wait) => {
let timeout;
return function () {
let context = this;
let args = arguments;
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => {
func.apply(context, args)
}, wait);
}
},
/**
* 节流
* @param func {*} 执行函数
* @param wait {*} 节流时间,毫秒
*/
throttle: (func, wait) => {
let previous = 0;
return function () {
let now = Date.now();
let context = this;
if (now - previous > wait) {
func.apply(context, arguments);
previous = now;
}
}
},
};
// 类型检测
function getType(obj) {
return Object.prototype.toString.call(obj).slice(8, -1);
}
3.4 http-модуль
Модуль http, по сути, представляет собой вторичную инкапсуляцию, основанную на аксиомах, добавляющую перехватчик и единообразно обрабатывающую все http-запросы и ответы через перехватчик. Настройте перехватчик http-запроса, единообразно настройте заголовок запроса, например токен, а затем настройте перехватчик ответа http, когда интерфейс вернет код состояния 401 Unauthorized (неавторизованный), позвольте пользователю вернуться на страницу входа.
/*
@file: http 请求库
@Author: tree
*/
import axios from 'axios';
import httpCode from '../../consts/httpCode';
import localStorage from '../localStorage'
const _axios = axios.create({});
_axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
_axios.interceptors.request.use(
(config) => {
if (localStorage.get('token')) {
config.headers.token = localStorage.get('token');
}
return config;
},
(err) => Promise.reject(err),
);
_axios.interceptors.response.use(
(response) => {
return response;
}, (error) => {
if (error && error.response) {
if (error.response.status === 401) {
//todo
}
}
return Promise.reject(error.response && error.response.data);
},
);
const request = function (url, params, config, method) {
return _axios[method](url, params, Object.assign({}, config))
.then(checkStatus).then(checkCode);
};
// 处理网络请求带来的校验
function checkStatus(response) {
// 如果 http 状态码正常, 则直接返回数据
if (response && (response.status === 200 || response.status === 304 || response.status === 400)) {
return response.data || httpCode.NET_ERROR
}
return httpCode.NET_ERROR
}
// 校验服务器返回数据
function checkCode(res) {
return res;
}
export default {
init: function (option = {withCredentials: true}) {
_axios.defaults.baseURL = option.url;
_axios.defaults.timeout = option.timeout || 20000;
_axios.defaults.withCredentials = option.withCredentials;
},
get: (url, params, config = {}) => request(url, params, config, 'get'),
post: (url, params, config = {}) => request(url, params, config, 'post'),
}
3.5 сторожевой модуль мониторинга
Sentry — это интерфейсный инструмент мониторинга исключений и отчетности с открытым исходным кодом.Интеграция в проект поможет вам собирать и регистрировать проблемы в различных средах (тестирование, производство и т. д.) и находить код, в котором находится проблема. также сделал часовых в проекте. поддержка
/*
* @file: sentry 异常上报日志监控
* @Author:tree,
* 常用配置 option:https://docs.sentry.io/clients/javascript/config/
* 1.自动捕获vue组件内异常
* 2.自动捕获promise内的异常
* 3.自动捕获没有被catch的运行异常
*/
import Raven from 'raven-js';
import RavenVue from 'raven-js/plugins/vue';
class Report {
constructor(Vue, options = {}) {
this.vue = Vue;
this.options = options;
}
static getInstance(Vue, Option) {
if (!(this.instance instanceof this)) {
this.instance = new this(Vue, Option);
this.instance.install();
}
return this.instance;
}
install() {
if (process.env.NODE_ENV !== 'development') {
Raven.config(this.options.dsn, {
environment: process.env.NODE_ENV,
}).addPlugin(RavenVue, this.Vue).install();
// raven内置了vue插件,会通过vue.config.errorHandler来捕获vue组件内错误并上报sentry服务
// 记录用户信息
Raven.setUserContext({user: this.options.user || ''});
// 设置全局tag标签
Raven.setTagsContext({environment: this.options.env || ''});
}
}
/**
* 主动上报
* type: 'info','warning','error'
*/
log(data = null, type = 'error', options = {}) {
// 添加面包屑
Raven.captureBreadcrumb({
message: data,
category: 'manual message',
});
// 异常上报
if (data instanceof Error) {
Raven.captureException(data, {
level: type,
logger: 'manual exception',
tags: {options},
});
} else {
Raven.captureException('error', {
level: type,
logger: 'manual data',
extra: {
data,
options: this.options,
date: new Date(),
},
});
}
}
}
export default Report;
3.6 require.context() автоматически вводит исходные файлы
Когда все модули разработаны, нам нужно экспортировать каждый модуль.Здесь мы используем require.context для обхода указанных файлов в папке, а затем автоматически импортируем их вместо импорта каждого модуля по отдельности.
// src/index.js
/*
* @author:tree
*/
let utils = {};
let haveDefault = ['http','sentry'];
const modules = require.context('./modules/', true, /.js$/);
modules.keys().forEach(modulesKey => {
let attr = modulesKey.replace('./', '').replace('.js', '').replace('/index', '');
if (haveDefault.includes(attr)) {
utils[attr] = modules(modulesKey).default;
}else {
utils[attr] = modules(modulesKey);
}
});
module.exports = utils;
Что касается использования require.context, require.context(). Он позволяет перейти в каталог для поиска, флаг, указывающий, следует ли искать также в подкаталогах, и регулярное выражение для сопоставления файлов, когда вы создаете проект, веб-пакет будет handle требует содержимого .context
require.context() может передавать три параметра:
- directory : путь для чтения файла из
- useSubdirectories : следует ли просматривать подкаталоги файлов
- regExp: регулярное выражение для сопоставления файлов
4. Модульное тестирование
После завершения модульной разработки библиотеки инструментов, чтобы обеспечить качество кода, проверять функциональную целостность каждого модуля, нам нужно тестировать каждый модуль, чтобы обеспечить функционировать правильно, а выпуск
Я использую инструмент разработки библиотеки в jest в качестве фреймворка модульного тестирования, Jest — это фреймворк модульного тестирования JS Facebook с открытым исходным кодом, дополнение Jest к базовым утверждениям и функциям Mock, а также снимок тестирования, отчеты о покрытии и другие полезные функции. , Узнайте больше о модульном тестировании.Портал 🚪
Позвольте мне использовать модуль даты в качестве случая, как протестировать этот модуль
4.1 конфигурационный файл jest
// jest.config.js
const path = require('path');
module.exports = {
verbose: true,
rootDir: path.resolve(__dirname, '../../'),
moduleFileExtensions: [
'js',
'json',
],
testMatch: [ // 匹配测试用例的文件
'<rootDir>/test/unit/specs/*.test.js',
],
transformIgnorePatterns: ['/node_modules/'],
};
4.2 Тестовые случаи
// date.test.js
const date = require('../../../src/modules/date');
describe('date 模块', () => {
test('formatTime()默认格式,返回时间格式是否正常', () => {
expect(date.formatTime(1586934316925)).toBe('2020-04-15 15:05:16');
})
test('formatTime()传参数,返回时间格式是否正常', () => {
expect(date.formatTime(1586934316925,'yyyy.MM.dd')).toBe('2020.04.15');
})
});
воплощать в жизньnpm run test
5. Команды скрипта
После завершения описанной выше серии разработок следующим шагом будет упаковка всех модулей в библиотеку инструментов, на этот раз настала очередь «скриптовой команды». Появляется главный герой
Определив команду скрипта в packjson следующим образом👇
{
"scripts": {
"build_rollup": "rollup -c",
"build": "webpack --config ./build/webpack.pro.config.js"
"test": "jest --config src/test/unit/jest.conf.js",
},
...
}
После настройки выполнитьnpm run build
После завершения выполнения сгенерированный kdutil.min.js появится в каталоге dist, который также является «входным файлом» библиотеки инструментов, окончательно загруженной в npm.
6. выпуск нпм
После завершения настроек вышеуказанных команд скрипта теперь последний шаг — «отправить пакет» и использовать npm для управления пакетами.
6.1 Настройте информацию, связанную с вашим пакетом, через packjson
//package.json
{
"name": "kdutil",
"version": "0.0.2", # 包的版本号,每次发布不能重复
"main": "dist/kdutil.min.js", # 打包完的目标文件
"author": "tree <shuxin_liu@kingdee.com>",
"keywords": [
"utils",
"tool",
"kdutil"
],
...
}
6.2 Написание документации по разработке readme.me
6.3 Выпуск
Сначала вам нужно войти в свою учетную запись npm, а затем выполнить команду публикации.
npm login # 登录你上面注册的npm账号
npm publish # 登录成功后,执行发布命令
+ kdutil@0.0.2 # 发布成功显示npm报名及包的版本号
7. Окончание
Через вышеизложенное мы завершим упрощенную версию библиотеки инструментов kdutil от 0 до 1, это адрес github.GitHub.com/маленькое дерево М…, если вы чувствуете, что это полезно для вас, поставьте звезду ✨, большое спасибо